196 lines
9.6 KiB
HTML
196 lines
9.6 KiB
HTML
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 12000;">
|
||
<div class="toast border-0 shadow" [class.show]="toastOpen" [class.text-bg-success]="toastType === 'success'" [class.text-bg-danger]="toastType === 'danger'">
|
||
<div class="toast-header border-bottom-0">
|
||
<strong class="me-auto">LineGestão</strong>
|
||
<button type="button" class="btn-close" (click)="hideToast()"></button>
|
||
</div>
|
||
<div class="toast-body bg-white rounded-bottom text-dark fw-bold">{{ toastMessage }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="vigencia-page">
|
||
<span class="page-blob blob-1"></span>
|
||
<span class="page-blob blob-2"></span>
|
||
<span class="page-blob blob-3"></span>
|
||
|
||
<div class="container-geral-responsive">
|
||
<div class="geral-card">
|
||
|
||
<div class="geral-header">
|
||
<div class="header-row-top">
|
||
<div class="title-badge"><i class="bi bi-calendar2-week"></i> VIGÊNCIA</div>
|
||
<div class="header-title">
|
||
<h5 class="title">GESTÃO DE VIGÊNCIA</h5>
|
||
<small class="subtitle">Controle de contratos e fidelização</small>
|
||
</div>
|
||
<div class="header-actions d-flex gap-2 justify-content-end">
|
||
<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 *ngIf="isSysAdmin" class="btn btn-brand btn-sm" (click)="openCreate()">
|
||
<i class="bi bi-plus-circle me-1"></i> Nova Vigência
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mureg-kpis mt-4 animate-fade-in" *ngIf="viewMode === 'groups'">
|
||
<div class="kpi">
|
||
<span class="lbl">Total Clientes</span>
|
||
<span class="val">{{ kpiTotalClientes }}</span>
|
||
</div>
|
||
<div class="kpi">
|
||
<span class="lbl">Total Linhas</span>
|
||
<span class="val">{{ kpiTotalLinhas }}</span>
|
||
</div>
|
||
<div class="kpi">
|
||
<span class="lbl text-danger">Total Vencidos</span>
|
||
<span class="val text-danger">{{ kpiTotalVencidos }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="controls mt-3 mb-2 d-flex flex-wrap gap-3 align-items-center justify-content-between">
|
||
<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"></i>
|
||
</span>
|
||
<input
|
||
type="search"
|
||
class="form-control"
|
||
placeholder="Pesquisar..."
|
||
inputmode="search"
|
||
enterkeyhint="search"
|
||
autocomplete="off"
|
||
[(ngModel)]="search"
|
||
(ngModelChange)="onSearchChange()">
|
||
<button
|
||
class="btn btn-outline-secondary btn-clear"
|
||
type="button"
|
||
*ngIf="search"
|
||
(click)="clearFilters()">
|
||
<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="font-size: 0.75rem;">Itens por pág:</span>
|
||
<app-select class="select-glass" size="sm" [options]="pageSizeOptions" [(ngModel)]="pageSize" (ngModelChange)="fetch(1)" [disabled]="loading" style="width: 80px;"></app-select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="geral-body">
|
||
<div class="groups-container">
|
||
|
||
<div class="text-center p-5" *ngIf="loading">
|
||
<div class="spinner-border text-brand" role="status"></div>
|
||
</div>
|
||
|
||
<div class="empty-state text-center p-5" *ngIf="!loading && groups.length === 0">
|
||
Nenhum dado encontrado.
|
||
</div>
|
||
|
||
<div *ngFor="let g of groups" class="client-group-card mb-3" [class.expanded]="expandedGroup === g.cliente">
|
||
|
||
<div class="group-header" (click)="toggleGroup(g)">
|
||
<div class="group-info">
|
||
<h6 class="group-title mb-0">{{ g.cliente }}</h6>
|
||
<div class="group-badges mt-1">
|
||
<span class="badge-pill total">{{ g.linhas }} Registros</span>
|
||
<span class="badge-pill danger" *ngIf="g.vencidos > 0">{{ g.vencidos }} Vencidos</span>
|
||
<span class="badge-pill ok" *ngIf="g.linhas - g.vencidos > 0">{{ g.linhas - g.vencidos }} Ativos</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-body-top d-flex justify-content-between align-items-center px-4 py-2 border-bottom bg-white">
|
||
<small class="text-muted fw-bold">Linhas do Cliente</small>
|
||
<span class="chip-muted">Total: {{ g.total | currency:'BRL' }}</span>
|
||
</div>
|
||
|
||
<div class="text-center p-4" *ngIf="expandedLoading">
|
||
<div class="spinner-border spinner-border-sm text-brand"></div>
|
||
</div>
|
||
|
||
<div class="table-wrap inner-table-wrap" *ngIf="!expandedLoading">
|
||
<table class="table table-modern align-middle text-center mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th>ITEM</th>
|
||
<th>LINHA</th>
|
||
<th>CONTA</th>
|
||
<th>USUÁRIO</th>
|
||
<th class="plano-col">PLANO</th>
|
||
<th>EFETIVAÇÃO</th>
|
||
<th>VENCIMENTO</th>
|
||
<th class="text-end">TOTAL</th>
|
||
<th class="actions-col">AÇÕES</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr *ngFor="let row of groupRows" class="table-row-item">
|
||
<td class="text-muted fw-bold">{{ row.item }}</td>
|
||
<td class="fw-black text-blue">{{ row.linha }}</td>
|
||
<td class="text-dark small">{{ row.conta || '-' }}</td>
|
||
<td class="text-muted small">{{ row.usuario || '-' }}</td>
|
||
<td class="text-muted small td-clip plano-col" [title]="row.planoContrato">{{ row.planoContrato || '-' }}</td>
|
||
|
||
<td class="text-muted small fw-bold">
|
||
{{ row.dtEfetivacaoServico ? (row.dtEfetivacaoServico | date:'dd/MM/yyyy') : '-' }}
|
||
</td>
|
||
|
||
<td class="fw-bold" [class.text-danger]="isVencido(row.dtTerminoFidelizacao)">
|
||
{{ row.dtTerminoFidelizacao ? (row.dtTerminoFidelizacao | date:'dd/MM/yyyy') : '-' }}
|
||
</td>
|
||
|
||
<td class="text-end fw-black text-dark">
|
||
{{ (row.total || 0) | currency:'BRL' }}
|
||
</td>
|
||
|
||
<td class="actions-col">
|
||
<div class="action-group justify-content-center">
|
||
<span class="renew-chip" *ngIf="row.autoRenewYears">{{ getRenewalBadge(row) }}</span>
|
||
<button
|
||
*ngIf="isAVencer(row.dtTerminoFidelizacao)"
|
||
class="btn btn-primary btn-xs"
|
||
(click)="scheduleAutoRenew(row)"
|
||
title="Renovar por mais 2 anos">
|
||
Renovar +2 anos
|
||
</button>
|
||
<button class="btn-icon primary" (click)="openDetails(row)" title="Ver Detalhes"><i class="bi bi-eye"></i></button>
|
||
<button *ngIf="isSysAdmin" class="btn-icon primary" (click)="openEdit(row)" title="Editar"><i class="bi bi-pencil-square"></i></button>
|
||
<button *ngIf="isSysAdmin" class="btn-icon danger" (click)="openDelete(row)" title="Excluir"><i class="bi bi-trash"></i></button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr *ngIf="groupRows.length === 0">
|
||
<td colspan="9" class="text-center py-4 text-muted fw-bold">Nenhuma linha encontrada.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="geral-footer">
|
||
<div class="small text-muted fw-bold">Mostrando {{ (page - 1) * pageSize + 1 }}–{{ (page * pageSize) > total ? total : (page * pageSize) }} de {{ total }} Clientes</div>
|
||
<nav>
|
||
<ul class="pagination pagination-sm mb-0 pagination-modern">
|
||
<li class="page-item" [class.disabled]="page <= 1"><button class="page-link" (click)="fetch(page - 1)">Anterior</button></li>
|
||
<li class="page-item active"><button class="page-link">{{ page }}</button></li>
|
||
<li class="page-item" [class.disabled]="page >= totalPages"><button class="page-link" (click)="fetch(page + 1)">Próxima</button></li>
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<app-vigencia-modals [vm]="$any(vm)"></app-vigencia-modals>
|