line-gestao-frontend/src/app/pages/dados-usuarios/dados-usuarios.html

440 lines
24 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.

<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="users-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-geral-responsive">
<div class="geral-card">
<div class="geral-header">
<div class="header-row-top">
<div class="title-badge" data-animate>
<i class="bi bi-people-fill"></i> DADOS PF/PJ
</div>
<div class="header-title" data-animate>
<h5 class="title mb-0">GESTÃO DE USUÁRIOS PF/PJ</h5>
<small class="subtitle">Base de dados separada por pessoa física e jurídica</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)="refresh()" [disabled]="loading">
<i class="bi bi-arrow-clockwise me-1"></i> Atualizar
</button>
<button *ngIf="isAdmin" type="button" class="btn btn-brand btn-sm" (click)="openCreate()">
<i class="bi bi-plus-circle me-1"></i> Novo Usuário
</button>
</div>
</div>
<div class="users-kpis mt-4 animate-fade-in">
<div class="kpi">
<span class="lbl">Total Usuários</span>
<span class="val">
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loading">{{ kpiTotalRegistros || 0 }}</span>
</span>
</div>
<div class="kpi">
<span class="lbl">Clientes Únicos</span>
<span class="val">
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loading">{{ kpiClientesUnicos || 0 }}</span>
</span>
</div>
<div class="kpi">
<span class="lbl text-success">{{ tipoFilter === 'PJ' ? 'Com CNPJ' : 'Com CPF' }}</span>
<span class="val text-success">
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loading">{{ tipoFilter === 'PJ' ? (kpiComCnpj || 0) : (kpiComCpf || 0) }}</span>
</span>
</div>
<div class="kpi">
<span class="lbl text-brand">Com E-mail</span>
<span class="val text-brand">
<span *ngIf="loading" class="spinner-border spinner-border-sm text-muted"></span>
<span *ngIf="!loading">{{ kpiComEmail || 0 }}</span>
</span>
</div>
</div>
<div class="controls mt-3 mb-2" data-animate>
<div class="filter-tabs tipo-filter-tabs">
<button type="button" class="filter-tab" [class.active]="tipoFilter === 'PF'" (click)="setTipoFilter('PF')">
Pessoa Física
</button>
<button type="button" class="filter-tab" [class.active]="tipoFilter === 'PJ'" (click)="setTipoFilter('PJ')">
Pessoa Jurídica
</button>
</div>
<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)]="search" (ngModelChange)="onSearch()" />
<button class="btn btn-outline-secondary btn-clear" type="button" (click)="clearFilters()" *ngIf="search"><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">
<app-select class="select-glass" size="sm" [options]="pageSizeOptions" [(ngModel)]="pageSize" (ngModelChange)="onPageSizeChange()" [disabled]="loading"></app-select>
</div>
</div>
</div>
</div>
<div class="geral-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 && groups.length === 0">
Nenhum cliente encontrado.
</div>
<div class="group-list" *ngIf="!loading">
<div *ngFor="let g of groups; trackBy: trackByCliente" class="client-group-card" [class.expanded]="expandedGroup === g.cliente">
<div class="group-header" (click)="toggleGroup(g)">
<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.totalRegistros }} Registros</span>
<span class="badge-pill ok" *ngIf="tipoFilter === 'PF' && g.comCpf > 0">{{ g.comCpf }} CPF</span>
<span class="badge-pill ok" *ngIf="tipoFilter === 'PJ' && g.comCnpj > 0">{{ g.comCnpj }} CNPJ</span>
<span class="badge-pill ok" *ngIf="g.comEmail > 0">{{ g.comEmail }} Email</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> Visualização detalhada</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>{{ tipoFilter === 'PJ' ? 'CNPJ' : 'CPF' }}</th>
<th>E-MAIL</th>
<th>CELULAR</th>
<th class="actions-col">AÇÕES</th>
</tr>
</thead>
<tbody>
<tr *ngIf="groupRows.length === 0">
<td colspan="6" class="text-center py-4 empty-state text-muted fw-bold">Nenhum registro encontrado.</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 text-blue">{{ r.linha }}</td>
<td class="small font-monospace">{{ tipoFilter === 'PJ' ? (r.cnpj || '-') : (r.cpf || '-') }}</td>
<td class="text-muted small td-clip" [title]="r.email">{{ r.email || '-' }}</td>
<td class="text-muted small">{{ r.celular || '-' }}</td>
<td>
<div class="action-group justify-content-center">
<button class="btn-icon primary" (click)="openDetails(r)" title="Ver Detalhes"><i class="bi bi-eye"></i></button>
<button *ngIf="isAdmin" class="btn-icon primary" (click)="openEdit(r)" title="Editar"><i class="bi bi-pencil-square"></i></button>
<button *ngIf="isAdmin" class="btn-icon danger" (click)="openDelete(r)" title="Excluir"><i class="bi bi-trash"></i></button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="geral-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="detailsOpen || editOpen || deleteOpen || createOpen" (click)="closeDetails(); closeEdit(); cancelDelete(); closeCreate()"></div>
<div class="modal-custom" *ngIf="detailsOpen || editOpen || deleteOpen || createOpen" (click)="closeDetails(); closeEdit(); cancelDelete(); closeCreate()">
<div *ngIf="detailsOpen" 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-person-vcard"></i></span>
Detalhes do Usuário
</div>
<button class="btn-icon" (click)="closeDetails()"><i class="bi bi-x-lg"></i></button>
</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-card-text me-2"></i> Informações</span></div>
<div class="box-body">
<div class="form-grid">
<div class="form-field span-2"><label>CLIENTE</label><div class="fw-bold">{{ selectedRow?.cliente }}</div></div>
<div class="form-field"><label>TIPO</label><div>{{ (selectedRow?.tipoPessoa || 'PF') === 'PJ' ? 'PESSOA JURÍDICA' : 'PESSOA FÍSICA' }}</div></div>
<div class="form-field span-2"><label>{{ (selectedRow?.tipoPessoa || 'PF') === 'PJ' ? 'RAZÃO SOCIAL' : 'NOME' }}</label><div>{{ (selectedRow?.tipoPessoa || 'PF') === 'PJ' ? (selectedRow?.razaoSocial || selectedRow?.cliente || '-') : (selectedRow?.nome || selectedRow?.cliente || '-') }}</div></div>
<div class="form-field"><label>LINHA</label><div class="fw-black text-blue fs-5">{{ selectedRow?.linha }}</div></div>
<div class="form-field"><label>ITEM</label><div>{{ selectedRow?.item }}</div></div>
<div class="form-field"><label>{{ (selectedRow?.tipoPessoa || 'PF') === 'PJ' ? 'CNPJ' : 'CPF' }}</label><div>{{ (selectedRow?.tipoPessoa || 'PF') === 'PJ' ? (selectedRow?.cnpj || '-') : (selectedRow?.cpf || '-') }}</div></div>
<div class="form-field"><label>RG</label><div>{{ selectedRow?.rg || '-' }}</div></div>
<div class="form-field span-2"><label>E-MAIL</label><div>{{ selectedRow?.email || '-' }}</div></div>
<div class="form-field"><label>CELULAR</label><div>{{ selectedRow?.celular || '-' }}</div></div>
<div class="form-field"><label>TELEFONE FIXO</label><div>{{ selectedRow?.telefoneFixo || '-' }}</div></div>
<div class="form-field span-2"><label>ENDEREÇO</label><div>{{ selectedRow?.endereco || '-' }}</div></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- CREATE MODAL -->
<div *ngIf="createOpen" class="modal-card modal-xl-custom create-modal" (click)="$event.stopPropagation()">
<div class="modal-header">
<div class="modal-title">
<span class="icon-bg primary-soft"><i class="bi bi-plus-circle"></i></span>
Novo Usuário
</div>
<button class="btn-icon" (click)="closeCreate()"><i class="bi bi-x-lg"></i></button>
</div>
<div class="modal-body modern-body bg-light-gray" *ngIf="createModel">
<div class="edit-sections">
<details open class="detail-box">
<summary class="box-header">
<span><i class="bi bi-link-45deg me-2"></i> Vínculo com GERAL</span>
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
</summary>
<div class="box-body">
<div class="form-grid">
<div class="form-field span-2">
<label>Cliente (GERAL)</label>
<app-select
class="form-select"
size="sm"
[options]="clientsFromGeral"
[(ngModel)]="createModel.selectedClient"
(ngModelChange)="onCreateClientChange()"
[disabled]="createClientsLoading"
></app-select>
</div>
<div class="form-field span-2">
<label>Linha (GERAL)</label>
<app-select
class="form-select"
size="sm"
[options]="lineOptionsCreate"
labelKey="label"
valueKey="id"
[(ngModel)]="createModel.mobileLineId"
(ngModelChange)="onCreateLineChange()"
[disabled]="createLinesLoading || !createModel.selectedClient"
></app-select>
</div>
</div>
</div>
</details>
<details open class="detail-box">
<summary class="box-header">
<span><i class="bi bi-person-vcard me-2"></i> Dados do Usuário</span>
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
</summary>
<div class="box-body">
<div class="form-grid user-modal-grid">
<div class="form-field span-2"><label>Cliente</label><input class="form-control form-control-sm" [(ngModel)]="createModel.cliente" /></div>
<div class="form-field field-tipo"><label>Tipo</label>
<app-select
class="form-select"
size="sm"
[options]="tipoPessoaOptions"
labelKey="label"
valueKey="value"
[(ngModel)]="createModel.tipoPessoa"
(ngModelChange)="onCreateTipoChange()">
</app-select>
</div>
<div class="form-field span-2" *ngIf="(createModel.tipoPessoa || 'PF') === 'PF'">
<label>Nome</label>
<input class="form-control form-control-sm" [(ngModel)]="createModel.nome" />
</div>
<div class="form-field span-2" *ngIf="(createModel.tipoPessoa || 'PF') === 'PJ'">
<label>Razão Social</label>
<input class="form-control form-control-sm" [(ngModel)]="createModel.razaoSocial" />
</div>
<div class="form-field field-line"><label>Linha</label><input class="form-control form-control-sm" inputmode="numeric" [(ngModel)]="createModel.linha" /></div>
<div class="form-field field-item field-auto">
<label>Item (Automático)</label>
<input class="form-control form-control-sm bg-light" type="number" [(ngModel)]="createModel.item" readonly title="Gerado automaticamente pelo sistema" />
<small class="field-hint">Gerado automaticamente pelo sistema</small>
</div>
<div class="form-field field-cpf-cnpj" *ngIf="(createModel.tipoPessoa || 'PF') === 'PF'"><label>CPF</label><input class="form-control form-control-sm" [(ngModel)]="createModel.cpf" /></div>
<div class="form-field field-cpf-cnpj" *ngIf="(createModel.tipoPessoa || 'PF') === 'PJ'"><label>CNPJ</label><input class="form-control form-control-sm" [(ngModel)]="createModel.cnpj" /></div>
<div class="form-field field-rg"><label>RG</label><input class="form-control form-control-sm" [(ngModel)]="createModel.rg" /></div>
<div class="form-field span-2"><label>E-mail</label><input class="form-control form-control-sm" [(ngModel)]="createModel.email" /></div>
<div class="form-field span-2"><label>Endereço</label><input class="form-control form-control-sm" [(ngModel)]="createModel.endereco" /></div>
<div class="form-field field-celular"><label>Celular</label><input class="form-control form-control-sm" [(ngModel)]="createModel.celular" /></div>
<div class="form-field field-telefone"><label>Telefone Fixo</label><input class="form-control form-control-sm" [(ngModel)]="createModel.telefoneFixo" /></div>
<div class="form-field span-2" *ngIf="(createModel.tipoPessoa || 'PF') === 'PF'"><label>Data de Nascimento</label><input class="form-control form-control-sm" type="date" [(ngModel)]="createDateNascimento" /></div>
</div>
</div>
</details>
</div>
</div>
<div class="modal-footer p-3 text-end border-top bg-white">
<button class="btn btn-glass btn-sm me-2" (click)="closeCreate()">Cancelar</button>
<button class="btn btn-brand btn-sm" [disabled]="createSaving" (click)="saveCreate()">
{{ createSaving ? 'Salvando...' : 'Salvar' }}
</button>
</div>
</div>
<!-- EDIT MODAL -->
<div *ngIf="editOpen" class="modal-card modal-xl-custom" (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 Usuário
</div>
<button class="btn-icon" (click)="closeEdit()"><i class="bi bi-x-lg"></i></button>
</div>
<div class="modal-body modern-body bg-light-gray" *ngIf="editModel">
<div class="edit-sections">
<details open class="detail-box">
<summary class="box-header">
<span><i class="bi bi-person-badge me-2"></i> Identificação</span>
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
</summary>
<div class="box-body">
<div class="form-grid user-modal-grid">
<div class="form-field span-2"><label>Cliente</label><input class="form-control form-control-sm" [(ngModel)]="editModel.cliente" /></div>
<div class="form-field field-tipo"><label>Tipo</label>
<app-select
class="form-select"
size="sm"
[options]="tipoPessoaOptions"
labelKey="label"
valueKey="value"
[(ngModel)]="editModel.tipoPessoa"
(ngModelChange)="onEditTipoChange()">
</app-select>
</div>
<div class="form-field span-2" *ngIf="(editModel.tipoPessoa || 'PF') === 'PF'">
<label>Nome</label>
<input class="form-control form-control-sm" [(ngModel)]="editModel.nome" />
</div>
<div class="form-field span-2" *ngIf="(editModel.tipoPessoa || 'PF') === 'PJ'">
<label>Razão Social</label>
<input class="form-control form-control-sm" [(ngModel)]="editModel.razaoSocial" />
</div>
<div class="form-field field-line"><label>Linha</label><input class="form-control form-control-sm" inputmode="numeric" [(ngModel)]="editModel.linha" /></div>
<div class="form-field field-item field-auto">
<label>Item (Automático)</label>
<input class="form-control form-control-sm bg-light" type="number" [(ngModel)]="editModel.item" readonly title="Gerado automaticamente pelo sistema" />
<small class="field-hint">Gerado automaticamente pelo sistema</small>
</div>
<div class="form-field field-cpf-cnpj" *ngIf="(editModel.tipoPessoa || 'PF') === 'PF'">
<label>CPF</label>
<input class="form-control form-control-sm" [(ngModel)]="editModel.cpf" />
</div>
<div class="form-field field-cpf-cnpj" *ngIf="(editModel.tipoPessoa || 'PF') === 'PJ'">
<label>CNPJ</label>
<input class="form-control form-control-sm" [(ngModel)]="editModel.cnpj" />
</div>
<div class="form-field field-rg"><label>RG</label><input class="form-control form-control-sm" [(ngModel)]="editModel.rg" /></div>
</div>
</div>
</details>
<details open class="detail-box">
<summary class="box-header">
<span><i class="bi bi-envelope-paper me-2"></i> Contato</span>
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
</summary>
<div class="box-body">
<div class="form-grid user-modal-grid contact-modal-grid">
<div class="form-field span-2"><label>E-mail</label><input class="form-control form-control-sm" type="email" [(ngModel)]="editModel.email" /></div>
<div class="form-field field-celular"><label>Celular</label><input class="form-control form-control-sm" [(ngModel)]="editModel.celular" /></div>
<div class="form-field field-telefone"><label>Telefone Fixo</label><input class="form-control form-control-sm" [(ngModel)]="editModel.telefoneFixo" /></div>
<div class="form-field span-2"><label>Endereço</label><input class="form-control form-control-sm" [(ngModel)]="editModel.endereco" /></div>
</div>
</div>
</details>
<details open class="detail-box">
<summary class="box-header">
<span><i class="bi bi-calendar-event me-2"></i> Complemento</span>
<i class="bi bi-chevron-down ms-auto transition-icon"></i>
</summary>
<div class="box-body">
<div class="form-grid">
<div class="form-field" *ngIf="(editModel.tipoPessoa || 'PF') === 'PF'">
<label>Data Nascimento</label>
<input class="form-control form-control-sm" type="date" [(ngModel)]="editDateNascimento" />
</div>
</div>
</div>
</details>
</div>
</div>
<div class="modal-footer p-3 text-end border-top bg-white">
<button class="btn btn-glass btn-sm me-2" (click)="closeEdit()">Cancelar</button>
<button class="btn btn-primary btn-sm" [disabled]="editSaving" (click)="saveEdit()">
{{ editSaving ? 'Salvando...' : 'Salvar' }}
</button>
</div>
</div>
<!-- DELETE MODAL -->
<div *ngIf="deleteOpen" class="modal-card modal-lg" (click)="$event.stopPropagation()">
<div class="modal-header">
<div class="modal-title">
<span class="icon-bg danger-soft"><i class="bi bi-trash"></i></span>
Remover Usuário
</div>
<button class="btn-icon" (click)="cancelDelete()"><i class="bi bi-x-lg"></i></button>
</div>
<div class="modal-body modern-body bg-light-gray">
<div class="confirm-delete">
<div class="confirm-icon"><i class="bi bi-trash"></i></div>
<p class="mb-0">Confirma remover o registro <strong>{{ deleteTarget?.linha }}</strong>?</p>
</div>
</div>
<div class="modal-footer p-3 text-end border-top bg-white">
<button class="btn btn-glass btn-sm me-2" (click)="cancelDelete()">Cancelar</button>
<button class="btn btn-danger btn-sm" (click)="confirmDelete()">Excluir</button>
</div>
</div>
</div>