470 lines
19 KiB
HTML
470 lines
19 KiB
HTML
<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="mureg-page">
|
||
<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-mureg">
|
||
<div class="mureg-card" data-animate>
|
||
|
||
<div class="mureg-header">
|
||
<div class="header-row-top">
|
||
<div class="title-badge" data-animate>
|
||
<i class="bi bi-table"></i> MUREG
|
||
</div>
|
||
|
||
<div class="header-title" data-animate>
|
||
<h5 class="title mb-0">MUREG</h5>
|
||
<small class="subtitle">Gestão de registros MUREG</small>
|
||
</div>
|
||
|
||
<div class="header-actions d-flex gap-2 justify-content-end" data-animate>
|
||
<button type="button" class="btn btn-brand btn-sm" (click)="onCreate()" [disabled]="loading">
|
||
<i class="bi bi-plus-circle me-1"></i> Nova Mureg
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mureg-kpis mt-4 animate-fade-in">
|
||
<div class="kpi">
|
||
<span class="lbl">Clientes</span>
|
||
<span class="val">
|
||
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
|
||
<span *ngIf="!loading">{{ total || 0 }}</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="kpi">
|
||
<span class="lbl">Registros</span>
|
||
<span class="val">
|
||
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
|
||
<span *ngIf="!loading">{{ groupLoadedRecords || 0 }}</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="kpi">
|
||
<span class="lbl text-brand">Trocas</span>
|
||
<span class="val text-brand">
|
||
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
|
||
<span *ngIf="!loading">{{ groupTotalTrocas || 0 }}</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="kpi">
|
||
<span class="lbl text-success">ICCID</span>
|
||
<span class="val text-success">
|
||
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
|
||
<span *ngIf="!loading">{{ groupTotalIccids || 0 }}</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<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;">
|
||
Itens por pág:
|
||
</span>
|
||
|
||
<div class="select-wrapper">
|
||
<select
|
||
class="form-select form-select-sm select-glass"
|
||
[(ngModel)]="pageSize"
|
||
(change)="onPageSizeChange()"
|
||
[disabled]="loading"
|
||
>
|
||
<option [ngValue]="10">10</option>
|
||
<option [ngValue]="20">20</option>
|
||
<option [ngValue]="50">50</option>
|
||
<option [ngValue]="100">100</option>
|
||
</select>
|
||
<i class="bi bi-chevron-down select-icon"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mureg-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"
|
||
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 swap" *ngIf="g.trocas > 0">{{ g.trocas }} Trocas</span>
|
||
<span class="badge-pill ok" *ngIf="g.comIccid > 0">{{ g.comIccid }} ICCID</span>
|
||
<span class="badge-pill warn" *ngIf="g.semIccid > 0">{{ g.semIccid }} Sem ICCID</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="d-flex justify-content-between align-items-center px-4 py-2 border-bottom bg-white">
|
||
<small class="text-muted fw-bold">Registros do Cliente</small>
|
||
<span class="chip-muted">
|
||
<i class="bi bi-info-circle me-1"></i> Use o botão à direita para editar
|
||
</span>
|
||
</div>
|
||
|
||
<div class="table-wrap inner-table-wrap">
|
||
<table class="table table-modern align-middle text-center mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th>ITEM</th>
|
||
<th>LINHA ANTIGA</th>
|
||
<th>LINHA NOVA</th>
|
||
<th>ICCID</th>
|
||
<th>DATA MUREG</th>
|
||
<th>SITUAÇÃO</th>
|
||
<th style="min-width: 80px;">AÇÕES</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="text-dark">{{ r.linhaAntiga || '-' }}</td>
|
||
<td class="fw-black text-blue">{{ r.linhaNova || '-' }}</td>
|
||
<td class="small font-monospace">{{ r.iccid || '-' }}</td>
|
||
<td class="text-muted small fw-bold">{{ displayValue('dataDaMureg', r.dataDaMureg) }}</td>
|
||
<td>
|
||
<span class="status-pill" [class.is-swap]="isTroca(r)" [class.is-same]="!isTroca(r)">
|
||
{{ isTroca(r) ? 'TROCA' : 'SEM TROCA' }}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<div class="action-group justify-content-center">
|
||
<button class="btn-icon primary" (click)="onEditar(r)" title="Editar Registro">
|
||
<i class="bi bi-pencil-square"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mureg-footer">
|
||
<div class="small text-muted fw-bold">Mostrando {{ pageStart }}–{{ pageEnd }} de {{ total }} Clientes</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>
|
||
|
||
<div class="modal-backdrop-custom" *ngIf="editOpen || createOpen" (click)="closeEdit(); closeCreate()"></div>
|
||
|
||
<!-- ============================== -->
|
||
<!-- EDIT MODAL -->
|
||
<!-- ============================== -->
|
||
<div class="modal-custom" *ngIf="editOpen">
|
||
<div class="modal-card modal-lg" (click)="$event.stopPropagation()">
|
||
|
||
<div class="modal-header">
|
||
<div class="modal-title">
|
||
<span class="icon-bg primary-soft"><i class="bi bi-pencil-square"></i></span>
|
||
Editar Registro Mureg
|
||
</div>
|
||
|
||
<div class="d-flex align-items-center gap-2">
|
||
<button class="btn btn-glass btn-sm" (click)="closeEdit()" [disabled]="editSaving">
|
||
<i class="bi bi-x-lg me-1"></i> Cancelar
|
||
</button>
|
||
|
||
<button class="btn btn-brand btn-sm" (click)="saveEdit()" [disabled]="!editModel || editSaving">
|
||
<span *ngIf="!editSaving"><i class="bi bi-check2-circle me-1"></i> Salvar</span>
|
||
<span *ngIf="editSaving"><span class="spinner-border spinner-border-sm me-2"></span> Salvando...</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-body modern-body bg-light-gray">
|
||
<ng-container *ngIf="editModel; else editLoadingTpl">
|
||
<div class="details-dashboard">
|
||
<div class="detail-box w-100">
|
||
|
||
<div class="box-header">
|
||
<span><i class="bi bi-card-text me-2"></i> Informações</span>
|
||
</div>
|
||
|
||
<div class="box-body">
|
||
<div class="form-grid">
|
||
|
||
<!-- Cliente (select) -->
|
||
<div class="form-field span-2">
|
||
<label>Cliente (GERAL)</label>
|
||
<select
|
||
class="form-control form-control-sm"
|
||
[(ngModel)]="editModel.selectedClient"
|
||
(change)="onEditClientChange()"
|
||
>
|
||
<option value="">Selecione...</option>
|
||
<option *ngFor="let c of clientOptions" [value]="c">{{ c }}</option>
|
||
</select>
|
||
|
||
<small class="text-muted fw-bold" *ngIf="editClientsLoading">
|
||
<span class="spinner-border spinner-border-sm me-2"></span>Carregando clientes...
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Linha Antiga (select da Geral) -->
|
||
<div class="form-field span-2">
|
||
<label>Linha Antiga (GERAL)</label>
|
||
<select
|
||
class="form-control form-control-sm"
|
||
[(ngModel)]="editModel.mobileLineId"
|
||
(change)="onEditLineChange()"
|
||
[disabled]="!editModel.selectedClient || editLinesLoading"
|
||
>
|
||
<option value="">Selecione a linha do cliente...</option>
|
||
|
||
<!-- ✅ ITEM • LINHA • USUÁRIO -->
|
||
<option *ngFor="let l of lineOptionsEdit" [value]="l.id">
|
||
{{ l.item }} • {{ l.linha || '-' }} • {{ l.usuario || 'SEM USUÁRIO' }}
|
||
</option>
|
||
</select>
|
||
|
||
<small class="text-muted fw-bold" *ngIf="editLinesLoading">
|
||
<span class="spinner-border spinner-border-sm me-2"></span>Carregando linhas...
|
||
</small>
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label>Item</label>
|
||
<input class="form-control form-control-sm" [(ngModel)]="editModel.item" />
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label>Data Mureg</label>
|
||
<input class="form-control form-control-sm" type="date" [(ngModel)]="editModel.dataDaMureg" />
|
||
</div>
|
||
|
||
<!-- LinhaAntiga (snapshot) - preenchido automaticamente -->
|
||
<div class="form-field">
|
||
<label>Linha Antiga (snapshot)</label>
|
||
<input class="form-control form-control-sm" [(ngModel)]="editModel.linhaAntiga" readonly />
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label>Linha Nova</label>
|
||
<input class="form-control form-control-sm fw-bold text-blue" [(ngModel)]="editModel.linhaNova" />
|
||
</div>
|
||
|
||
<!-- ICCID auto do GERAL -->
|
||
<div class="form-field span-2">
|
||
<label>ICCID (auto)</label>
|
||
<input class="form-control form-control-sm font-monospace" [(ngModel)]="editModel.iccid" readonly />
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="mt-3" *ngIf="editModel?.clienteInfo">
|
||
<small class="text-muted fw-bold">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{{ editModel.clienteInfo }}
|
||
</small>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ng-container>
|
||
|
||
<ng-template #editLoadingTpl>
|
||
<div class="p-5 text-center text-muted">
|
||
<span class="spinner-border me-2"></span> Preparando edição...
|
||
</div>
|
||
</ng-template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================== -->
|
||
<!-- CREATE MODAL -->
|
||
<!-- ============================== -->
|
||
<div class="modal-custom" *ngIf="createOpen">
|
||
<div class="modal-card modal-lg" (click)="$event.stopPropagation()">
|
||
|
||
<div class="modal-header">
|
||
<div class="modal-title">
|
||
<span class="icon-bg brand-soft"><i class="bi bi-plus-lg"></i></span>
|
||
Nova Mureg
|
||
</div>
|
||
|
||
<div class="d-flex align-items-center gap-2">
|
||
<button class="btn btn-glass btn-sm" (click)="closeCreate()" [disabled]="createSaving">
|
||
<i class="bi bi-x-lg me-1"></i> Cancelar
|
||
</button>
|
||
|
||
<button class="btn btn-brand btn-sm" (click)="saveCreate()" [disabled]="createSaving">
|
||
<span *ngIf="!createSaving"><i class="bi bi-check2-circle me-1"></i> Criar</span>
|
||
<span *ngIf="createSaving"><span class="spinner-border spinner-border-sm me-2"></span> Salvando...</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-body modern-body bg-light-gray">
|
||
<div class="details-dashboard">
|
||
<div class="detail-box w-100">
|
||
|
||
<div class="box-header">
|
||
<span><i class="bi bi-pencil me-2"></i> Preencha os dados</span>
|
||
</div>
|
||
|
||
<div class="box-body">
|
||
<div class="form-grid">
|
||
|
||
<!-- Cliente (select) -->
|
||
<div class="form-field span-2">
|
||
<label>Cliente (GERAL) <span class="text-danger">*</span></label>
|
||
<select
|
||
class="form-control form-control-sm"
|
||
[(ngModel)]="createModel.selectedClient"
|
||
(change)="onCreateClientChange()"
|
||
>
|
||
<option value="">Selecione...</option>
|
||
<option *ngFor="let c of clientOptions" [value]="c">{{ c }}</option>
|
||
</select>
|
||
|
||
<small class="text-muted fw-bold" *ngIf="createClientsLoading">
|
||
<span class="spinner-border spinner-border-sm me-2"></span>Carregando clientes...
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Linha Antiga (select Geral) -->
|
||
<div class="form-field span-2">
|
||
<label>Linha Antiga (GERAL) <span class="text-danger">*</span></label>
|
||
<select
|
||
class="form-control form-control-sm"
|
||
[(ngModel)]="createModel.mobileLineId"
|
||
(change)="onCreateLineChange()"
|
||
[disabled]="!createModel.selectedClient || createLinesLoading"
|
||
>
|
||
<option value="">Selecione a linha do cliente...</option>
|
||
|
||
<!-- ✅ ITEM • LINHA • USUÁRIO -->
|
||
<option *ngFor="let l of lineOptionsCreate" [value]="l.id">
|
||
{{ l.item }} • {{ l.linha || '-' }} • {{ l.usuario || 'SEM USUÁRIO' }}
|
||
</option>
|
||
</select>
|
||
|
||
<small class="text-muted fw-bold" *ngIf="createLinesLoading">
|
||
<span class="spinner-border spinner-border-sm me-2"></span>Carregando linhas...
|
||
</small>
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label>Item</label>
|
||
<input class="form-control form-control-sm" [(ngModel)]="createModel.item" />
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label>Data Mureg</label>
|
||
<input class="form-control form-control-sm" type="date" [(ngModel)]="createModel.dataDaMureg" />
|
||
</div>
|
||
|
||
<!-- snapshot -->
|
||
<div class="form-field">
|
||
<label>Linha Antiga (snapshot)</label>
|
||
<input class="form-control form-control-sm" [(ngModel)]="createModel.linhaAntiga" readonly />
|
||
</div>
|
||
|
||
<div class="form-field">
|
||
<label>Linha Nova <span class="text-danger">*</span></label>
|
||
<input class="form-control form-control-sm fw-bold text-blue" [(ngModel)]="createModel.linhaNova" />
|
||
</div>
|
||
|
||
<!-- ICCID auto do GERAL -->
|
||
<div class="form-field span-2">
|
||
<label>ICCID (auto)</label>
|
||
<input class="form-control form-control-sm font-monospace" [(ngModel)]="createModel.iccid" readonly />
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="mt-3" *ngIf="createModel?.clienteInfo">
|
||
<small class="text-muted fw-bold">
|
||
<i class="bi bi-info-circle me-1"></i>
|
||
{{ createModel.clienteInfo }}
|
||
</small>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|