Repeat KYC document uploads create a new client_identifier row rather than updating in place, so loadExistingKycIdentifier() must prefer the newest matching row instead of the first one returned by the API. Also swap the Clients list "Account No." column to show the client's numeric ID instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
187 lines
7.1 KiB
HTML
187 lines
7.1 KiB
HTML
<!--
|
|
Copyright since 2025 Mifos Initiative
|
|
|
|
This Source Code Form is subject to the terms of the Mozilla Public
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
-->
|
|
|
|
<mat-card class="container">
|
|
<!-- Header Bar -->
|
|
<div class="table-header">
|
|
<div class="table-header-row">
|
|
<div class="table-title-group">
|
|
<h2 class="table-title">
|
|
<div class="t-logomark"><span></span><span></span><span></span><span></span></div>
|
|
{{ 'labels.menus.Clients' | translate }}
|
|
</h2>
|
|
<span class="table-count" *ngIf="totalRows">{{ totalRows }} records</span>
|
|
</div>
|
|
|
|
<div class="table-buttons">
|
|
<button
|
|
mat-stroked-button
|
|
[routerLink]="['../', 'organization', 'bulk-import', 'Clients']"
|
|
*mifosxHasPermission="'CREATE_CLIENT'"
|
|
class="btn-import"
|
|
>
|
|
<fa-icon icon="download"></fa-icon>
|
|
{{ 'labels.buttons.Import Client' | translate }}
|
|
</button>
|
|
<button
|
|
mat-flat-button
|
|
color="primary"
|
|
[routerLink]="['create']"
|
|
*mifosxHasPermission="'CREATE_CLIENT'"
|
|
class="btn-create"
|
|
>
|
|
<fa-icon icon="plus"></fa-icon>
|
|
{{ 'labels.buttons.Create Client' | translate }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-filters">
|
|
<mat-form-field class="table-search" appearance="outline">
|
|
<input
|
|
matInput
|
|
placeholder="{{ 'labels.text.SearchByClient' | translate }}"
|
|
(keydown.enter)="search($event.target.value)"
|
|
/>
|
|
</mat-form-field>
|
|
|
|
<mat-form-field class="table-filter" appearance="outline">
|
|
<mat-select [(value)]="statusFilter" (selectionChange)="applyClientFilters()" placeholder="Status">
|
|
<mat-option value="">All Status</mat-option>
|
|
<mat-option value="active">Active</mat-option>
|
|
<mat-option value="pending">Pending</mat-option>
|
|
<mat-option value="closed">Closed</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
|
|
<mat-form-field class="table-filter" appearance="outline">
|
|
<mat-select [(value)]="legalFormFilter" (selectionChange)="applyClientFilters()" placeholder="Legal Form">
|
|
<mat-option value="">All Forms</mat-option>
|
|
<mat-option value="1">Individual</mat-option>
|
|
<mat-option value="2">Non-Individual</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
|
|
<mat-form-field class="table-filter" appearance="outline">
|
|
<mat-select [(value)]="ckycFilter" (selectionChange)="applyClientFilters()" placeholder="CKYC">
|
|
<mat-option value="">All CKYC</mat-option>
|
|
<mat-option value="done">CKYC Done</mat-option>
|
|
<mat-option value="pending">CKYC Pending</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div [hidden]="!existsClientsToFilter" class="table-container">
|
|
@if (isLoading) {
|
|
<mat-progress-bar mode="indeterminate" class="table-loader"></mat-progress-bar>
|
|
}
|
|
|
|
<table mat-table [dataSource]="dataSource" matSort (matSortChange)="sortChanged($event)" class="modern-table">
|
|
<!-- Name Column -->
|
|
<ng-container matColumnDef="displayName">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
|
|
<td mat-cell *matCellDef="let row" class="cell-link">
|
|
<mat-icon class="row-legal-icon" [ngClass]="row.legalForm?.id === 2 ? 'entity' : 'person'">{{
|
|
row.legalForm?.id === 2 ? 'business' : 'person'
|
|
}}</mat-icon>
|
|
{{ hideClientData ? maskName(row.displayName) : row.displayName }}
|
|
@if (row._ckycDone) {
|
|
<mat-icon class="row-ckyc-icon" matTooltip="CKYC Done">verified</mat-icon>
|
|
}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Client ID Column -->
|
|
<ng-container matColumnDef="accountNumber">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>Client ID</th>
|
|
<td mat-cell *matCellDef="let row" class="cell-mono">{{ row.id }}</td>
|
|
</ng-container>
|
|
|
|
<!-- CIF Number Column -->
|
|
<ng-container matColumnDef="cifNumber">
|
|
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.CIF Number' | translate }}</th>
|
|
<td mat-cell *matCellDef="let row" class="cell-mono">{{ row.cifNumber }}</td>
|
|
</ng-container>
|
|
|
|
<!-- Legal Form Column -->
|
|
<ng-container matColumnDef="legalForm">
|
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Legal Form' | translate }}</th>
|
|
<td mat-cell *matCellDef="let row">
|
|
<span class="legal-form-badge" [ngClass]="row.legalForm?.id === 2 ? 'entity' : 'person'">
|
|
{{ row.legalForm?.id === 2 ? 'Non-Individual' : 'Individual' }}
|
|
</span>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Status Column -->
|
|
<ng-container matColumnDef="status">
|
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Status' | translate }}</th>
|
|
<td mat-cell *matCellDef="let row">
|
|
<span class="status-badge" [ngClass]="row.status.code | statusLookup">
|
|
{{ row.status.value | translate }}
|
|
</span>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Profile Completion Column -->
|
|
<ng-container matColumnDef="profileCompletion">
|
|
<th mat-header-cell *matHeaderCellDef>Profile</th>
|
|
<td mat-cell *matCellDef="let row">
|
|
<div class="profile-steps">
|
|
@for (step of getProfileSteps(row); track step.label) {
|
|
<span
|
|
class="step-dot"
|
|
[class.done]="step.done"
|
|
[matTooltip]="step.label + (step.done ? ' ✓' : ' — Pending')"
|
|
>
|
|
{{ step.label.charAt(0) }}
|
|
</span>
|
|
}
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Actions Column -->
|
|
<ng-container matColumnDef="actions">
|
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
<td mat-cell *matCellDef="let row" (click)="$event.stopPropagation()">
|
|
<div class="action-icons">
|
|
<button mat-button class="action-chip action-chip--view" [routerLink]="[row.id, 'general']">
|
|
<mat-icon>visibility</mat-icon>
|
|
View
|
|
</button>
|
|
<button mat-button class="action-chip action-chip--edit" [routerLink]="[row.id, 'edit']">
|
|
<mat-icon>edit_note</mat-icon>
|
|
Edit
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
|
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row"></tr>
|
|
</table>
|
|
|
|
<mat-paginator
|
|
[pageSize]="pageSize"
|
|
[pageSizeOptions]="[5, 50, 100]"
|
|
[length]="totalRows"
|
|
(page)="pageChanged($event)"
|
|
showFirstLastButtons
|
|
></mat-paginator>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div class="empty-state" *ngIf="notExistsClientsToFilter">
|
|
<fa-icon icon="search" class="empty-icon"></fa-icon>
|
|
<p class="empty-text">{{ 'labels.text.No client was found' | translate }}</p>
|
|
</div>
|
|
</mat-card>
|