- Current Account module with full CRUD, transactions, charges, hold/release, block/unblock - Current Products management under Products module - DIP mobile number verification with real API call to /lookup/check - Sanctions verification dialog - Charges support for Current Account (chargeAppliesTo=5) in create/edit - Fixed permissions from SAVINGSACCOUNT to CURRENTACCOUNT across all current account components - Added all missing translation keys for Current Account in en-US.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
386 lines
18 KiB
HTML
386 lines
18 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/.
|
|
-->
|
|
|
|
<div class="rbi-container">
|
|
<!-- Header -->
|
|
<div class="rbi-header">
|
|
<div class="rbi-header-left">
|
|
<div class="rbi-title">
|
|
<fa-icon icon="newspaper" class="title-icon"></fa-icon>
|
|
<h2>RBI Circulars & Notifications</h2>
|
|
</div>
|
|
<span class="rbi-subtitle">Stay updated with the latest Reserve Bank of India communications</span>
|
|
</div>
|
|
<div class="rbi-header-actions">
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
(click)="refreshFeeds()"
|
|
[disabled]="isRefreshing"
|
|
matTooltip="Fetch latest feeds from RBI"
|
|
>
|
|
<fa-icon icon="sync" class="mr-05"></fa-icon>
|
|
{{ isRefreshing ? 'Fetching...' : 'Refresh Feeds' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search Bar -->
|
|
<div class="search-bar">
|
|
<mat-form-field class="search-field" appearance="outline">
|
|
<mat-label>Search circulars...</mat-label>
|
|
<input
|
|
matInput
|
|
[value]="searchText"
|
|
(input)="onSearchChange($any($event.target).value)"
|
|
placeholder="Search by title or description"
|
|
/>
|
|
<fa-icon matSuffix icon="search" class="search-icon"></fa-icon>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!-- Tabs always visible -->
|
|
<mat-tab-group animationDuration="200ms" class="rbi-tabs">
|
|
<mat-tab *ngFor="let tab of tabs">
|
|
<!-- Tab Label -->
|
|
<ng-template mat-tab-label>
|
|
<div class="tab-label">
|
|
<fa-icon [icon]="tab.icon" class="tab-icon" [style.color]="tab.color"></fa-icon>
|
|
<span>{{ tab.label }}</span>
|
|
<span class="tab-badge" [style.background]="tab.color">{{ tab.dataSource.filteredData.length }}</span>
|
|
</div>
|
|
</ng-template>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content">
|
|
<!-- Loading State -->
|
|
<div *ngIf="isLoading" class="loading-state">
|
|
<mat-spinner diameter="40"></mat-spinner>
|
|
<p>Loading RBI circulars...</p>
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div *ngIf="errorMessage && !isLoading" class="error-state">
|
|
<fa-icon icon="exclamation-triangle" class="error-icon"></fa-icon>
|
|
<p>{{ errorMessage }}</p>
|
|
<button mat-raised-button color="primary" (click)="loadFeeds()">
|
|
<fa-icon icon="sync" class="mr-05"></fa-icon> Retry
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Empty state for this tab -->
|
|
<div *ngIf="!isLoading && !errorMessage && tab.dataSource.filteredData.length === 0" class="empty-state">
|
|
<fa-icon icon="archive" class="empty-icon"></fa-icon>
|
|
<h3>No {{ tab.label.toLowerCase() }} found</h3>
|
|
<p *ngIf="searchText">No results matching "{{ searchText }}"</p>
|
|
<p *ngIf="!searchText">Click "Refresh Feeds" to fetch the latest RBI communications</p>
|
|
</div>
|
|
|
|
<!-- Data Table -->
|
|
<div *ngIf="!isLoading && !errorMessage && tab.dataSource.filteredData.length > 0" class="table-wrapper">
|
|
<table mat-table [dataSource]="tab.dataSource" class="feed-table">
|
|
<!-- S.No Column -->
|
|
<ng-container matColumnDef="sno">
|
|
<th mat-header-cell *matHeaderCellDef class="col-sno">S.No</th>
|
|
<td mat-cell *matCellDef="let row" class="col-sno">
|
|
{{ getRowIndex(tab, row) }}
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Title Column -->
|
|
<ng-container matColumnDef="title">
|
|
<th mat-header-cell *matHeaderCellDef>Title</th>
|
|
<td mat-cell *matCellDef="let row" class="col-title">
|
|
<div class="title-cell">
|
|
<span class="new-badge" *ngIf="isNew(row.pubDate)">NEW</span>
|
|
<span class="title-text">{{ row.title }}</span>
|
|
</div>
|
|
<div *ngIf="row.description" class="description-text">{{ stripHtml(row.description) }}</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Date Column -->
|
|
<ng-container matColumnDef="pubDate">
|
|
<th mat-header-cell *matHeaderCellDef class="col-date">Published Date</th>
|
|
<td mat-cell *matCellDef="let row" class="col-date">
|
|
<div class="date-cell">
|
|
<fa-icon icon="calendar-alt" class="date-icon"></fa-icon>
|
|
{{ formatDate(row.pubDate) }}
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- Actions Column -->
|
|
<ng-container matColumnDef="actions">
|
|
<th mat-header-cell *matHeaderCellDef class="col-actions">Link</th>
|
|
<td mat-cell *matCellDef="let row" class="col-actions">
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
class="view-btn"
|
|
(click)="openLink(row.link)"
|
|
matTooltip="Open on RBI website"
|
|
>
|
|
<fa-icon icon="external-link-alt" class="mr-05"></fa-icon>
|
|
View
|
|
</button>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<!-- OpenProject Details Column (Notification tab only) -->
|
|
<ng-container matColumnDef="opDetails">
|
|
<th mat-header-cell *matHeaderCellDef class="col-op-details">OpenProject</th>
|
|
<td mat-cell *matCellDef="let row" class="col-op-details">
|
|
<!-- No work package linked -->
|
|
<span *ngIf="!row.openProjectWpId" class="op-no-link">—</span>
|
|
|
|
<!-- Fetch button (only when openProjectWpId exists) -->
|
|
<button
|
|
*ngIf="row.openProjectWpId && !opRowDetails[row.id] && !opRowLoading[row.id]"
|
|
mat-stroked-button
|
|
color="accent"
|
|
class="op-fetch-btn"
|
|
(click)="fetchOpDetailsForRow(row)"
|
|
[matTooltip]="'Fetch OpenProject work item #' + row.openProjectWpId"
|
|
>
|
|
<fa-icon icon="tasks" class="mr-05"></fa-icon>
|
|
#{{ row.openProjectWpId }}
|
|
</button>
|
|
|
|
<!-- Loading spinner -->
|
|
<div *ngIf="opRowLoading[row.id]" class="op-row-loading">
|
|
<mat-progress-spinner diameter="20" mode="indeterminate"></mat-progress-spinner>
|
|
</div>
|
|
|
|
<!-- Details result -->
|
|
<div *ngIf="opRowDetails[row.id]" class="op-row-result">
|
|
<div class="op-row-summary">
|
|
<span class="op-row-id">#{{ opRowDetails[row.id].id }}</span>
|
|
<span class="op-status-chip" [style.background-color]="getStatusColor(opRowDetails[row.id].status)">
|
|
{{ opRowDetails[row.id].status }}
|
|
</span>
|
|
</div>
|
|
<div class="op-row-subject">{{ opRowDetails[row.id].subject }}</div>
|
|
<div class="op-row-meta">
|
|
<span *ngIf="opRowDetails[row.id].assignee">
|
|
<fa-icon icon="user" class="op-info-icon"></fa-icon> {{ opRowDetails[row.id].assignee }}
|
|
</span>
|
|
<span *ngIf="opRowDetails[row.id].priority">
|
|
<fa-icon icon="flag" class="op-info-icon"></fa-icon> {{ opRowDetails[row.id].priority }}
|
|
</span>
|
|
</div>
|
|
<div class="op-row-actions">
|
|
<button
|
|
mat-button
|
|
color="primary"
|
|
class="op-view-detail-btn"
|
|
(click)="viewWorkPackageDetails(opRowDetails[row.id].id)"
|
|
matTooltip="View full details"
|
|
>
|
|
<fa-icon icon="info-circle" class="mr-05"></fa-icon> Full Details
|
|
</button>
|
|
<button
|
|
*ngIf="opRowDetails[row.id].openProjectUrl"
|
|
mat-button
|
|
color="primary"
|
|
class="op-view-detail-btn"
|
|
(click)="openInOpenProject(opRowDetails[row.id])"
|
|
matTooltip="Open in OpenProject"
|
|
>
|
|
<fa-icon icon="external-link-alt" class="mr-05"></fa-icon> Open
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</ng-container>
|
|
|
|
<tr mat-header-row *matHeaderRowDef="getColumnsForTab(tab)"></tr>
|
|
<tr mat-row *matRowDef="let row; columns: getColumnsForTab(tab)" class="feed-row"></tr>
|
|
</table>
|
|
|
|
<mat-paginator
|
|
[pageSize]="10"
|
|
[pageSizeOptions]="[5, 10, 25, 50]"
|
|
showFirstLastButtons
|
|
></mat-paginator>
|
|
</div>
|
|
</div>
|
|
</mat-tab>
|
|
|
|
<!-- OpenProject Work Items Tab -->
|
|
<mat-tab *ngIf="openProjectEnabled">
|
|
<ng-template mat-tab-label>
|
|
<div class="tab-label">
|
|
<fa-icon icon="tasks" class="tab-icon" style="color: #6a1b9a"></fa-icon>
|
|
<span>OpenProject</span>
|
|
<span class="tab-badge" style="background: #6a1b9a">{{ opFilteredWorkPackages.length }}</span>
|
|
</div>
|
|
</ng-template>
|
|
|
|
<div class="tab-content">
|
|
<!-- Not loaded yet -->
|
|
<div *ngIf="!opLoading && opWorkPackages.length === 0 && !opError" class="op-start-section">
|
|
<fa-icon icon="project-diagram" class="op-start-icon"></fa-icon>
|
|
<h3>OpenProject Work Items</h3>
|
|
<p>Fetch work items from your OpenProject instance to view and track project tasks.</p>
|
|
<button mat-raised-button color="primary" (click)="loadWorkPackages()">
|
|
<fa-icon icon="download" class="mr-05"></fa-icon>
|
|
Fetch Work Items
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div *ngIf="opLoading" class="loading-state">
|
|
<mat-spinner diameter="40"></mat-spinner>
|
|
<p>Loading OpenProject work items...</p>
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div *ngIf="opError && !opLoading" class="error-state">
|
|
<fa-icon icon="exclamation-triangle" class="error-icon"></fa-icon>
|
|
<p>{{ opError }}</p>
|
|
<button mat-raised-button color="primary" (click)="loadWorkPackages()">
|
|
<fa-icon icon="sync" class="mr-05"></fa-icon> Retry
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Work Packages List -->
|
|
<div *ngIf="!opLoading && !opError && opWorkPackages.length > 0">
|
|
<!-- Search within OpenProject -->
|
|
<div class="op-toolbar">
|
|
<mat-form-field class="op-search-field" appearance="outline">
|
|
<mat-label>Search work items...</mat-label>
|
|
<input matInput (input)="filterWorkPackages($any($event.target).value)" placeholder="Search by subject, status, or type" />
|
|
<fa-icon matSuffix icon="search" class="search-icon"></fa-icon>
|
|
</mat-form-field>
|
|
<button mat-stroked-button color="primary" (click)="loadWorkPackages()" matTooltip="Refresh work items">
|
|
<fa-icon icon="sync" class="mr-05"></fa-icon> Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Work Packages Cards -->
|
|
<div class="op-grid">
|
|
<div *ngFor="let wp of opFilteredWorkPackages" class="op-card" (click)="viewWorkPackageDetails(wp.id)">
|
|
<div class="op-card-header">
|
|
<span class="op-card-id">#{{ wp.id }}</span>
|
|
<span class="op-card-type" [style.color]="getPriorityColor(wp.priority)">{{ wp.type }}</span>
|
|
</div>
|
|
<h4 class="op-card-subject">{{ wp.subject }}</h4>
|
|
<div class="op-card-meta">
|
|
<span class="op-status-chip" [style.background-color]="getStatusColor(wp.status)">
|
|
{{ wp.status }}
|
|
</span>
|
|
<span class="op-priority-chip" [style.border-color]="getPriorityColor(wp.priority)" [style.color]="getPriorityColor(wp.priority)">
|
|
{{ wp.priority }}
|
|
</span>
|
|
</div>
|
|
<div class="op-card-info">
|
|
<span *ngIf="wp.assignee"><fa-icon icon="user" class="op-info-icon"></fa-icon> {{ wp.assignee }}</span>
|
|
<span *ngIf="wp.dueDate"><fa-icon icon="calendar-alt" class="op-info-icon"></fa-icon> {{ formatOpDate(wp.dueDate) }}</span>
|
|
<span><fa-icon icon="folder" class="op-info-icon"></fa-icon> {{ wp.project }}</span>
|
|
</div>
|
|
<div *ngIf="wp.percentageDone !== undefined && wp.percentageDone > 0" class="op-progress">
|
|
<mat-progress-bar mode="determinate" [value]="wp.percentageDone"></mat-progress-bar>
|
|
<span class="op-progress-label">{{ wp.percentageDone }}%</span>
|
|
</div>
|
|
<div class="op-card-actions">
|
|
<button mat-button color="primary" (click)="openInOpenProject(wp); $event.stopPropagation()" matTooltip="Open in OpenProject">
|
|
<fa-icon icon="external-link-alt" class="mr-05"></fa-icon> Open in OpenProject
|
|
</button>
|
|
<button mat-button color="accent" (click)="viewWorkPackageDetails(wp.id); $event.stopPropagation()" matTooltip="Fetch full details via API">
|
|
<fa-icon icon="info-circle" class="mr-05"></fa-icon> Fetch Details
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="results-info">Showing {{ opFilteredWorkPackages.length }} of {{ opWorkPackages.length }} work items</div>
|
|
</div>
|
|
|
|
<!-- Work Package Detail Panel -->
|
|
<div *ngIf="opSelectedWorkPackage" class="op-detail-overlay" (click)="closeWorkPackageDetails()">
|
|
<div class="op-detail-panel" (click)="$event.stopPropagation()">
|
|
<div class="op-detail-header">
|
|
<h3>
|
|
<span class="op-card-id">#{{ opSelectedWorkPackage.id }}</span>
|
|
{{ opSelectedWorkPackage.subject }}
|
|
</h3>
|
|
<button mat-icon-button (click)="closeWorkPackageDetails()">
|
|
<mat-icon>close</mat-icon>
|
|
</button>
|
|
</div>
|
|
<div class="op-detail-body">
|
|
<div class="op-detail-row">
|
|
<span class="op-detail-label">Status</span>
|
|
<span class="op-status-chip" [style.background-color]="getStatusColor(opSelectedWorkPackage.status)">
|
|
{{ opSelectedWorkPackage.status }}
|
|
</span>
|
|
</div>
|
|
<div class="op-detail-row">
|
|
<span class="op-detail-label">Type</span>
|
|
<span class="op-detail-value">{{ opSelectedWorkPackage.type }}</span>
|
|
</div>
|
|
<div class="op-detail-row">
|
|
<span class="op-detail-label">Priority</span>
|
|
<span class="op-priority-chip" [style.border-color]="getPriorityColor(opSelectedWorkPackage.priority)" [style.color]="getPriorityColor(opSelectedWorkPackage.priority)">
|
|
{{ opSelectedWorkPackage.priority }}
|
|
</span>
|
|
</div>
|
|
<div class="op-detail-row">
|
|
<span class="op-detail-label">Project</span>
|
|
<span class="op-detail-value">{{ opSelectedWorkPackage.project }}</span>
|
|
</div>
|
|
<div *ngIf="opSelectedWorkPackage.assignee" class="op-detail-row">
|
|
<span class="op-detail-label">Assignee</span>
|
|
<span class="op-detail-value">{{ opSelectedWorkPackage.assignee }}</span>
|
|
</div>
|
|
<div class="op-detail-row">
|
|
<span class="op-detail-label">Created</span>
|
|
<span class="op-detail-value">{{ formatOpDate(opSelectedWorkPackage.createdAt) }}</span>
|
|
</div>
|
|
<div class="op-detail-row">
|
|
<span class="op-detail-label">Updated</span>
|
|
<span class="op-detail-value">{{ formatOpDate(opSelectedWorkPackage.updatedAt) }}</span>
|
|
</div>
|
|
<div *ngIf="opSelectedWorkPackage.dueDate" class="op-detail-row">
|
|
<span class="op-detail-label">Due Date</span>
|
|
<span class="op-detail-value">{{ formatOpDate(opSelectedWorkPackage.dueDate) }}</span>
|
|
</div>
|
|
<div *ngIf="opSelectedWorkPackage.percentageDone !== undefined" class="op-detail-row">
|
|
<span class="op-detail-label">Progress</span>
|
|
<div class="op-detail-progress">
|
|
<mat-progress-bar mode="determinate" [value]="opSelectedWorkPackage.percentageDone"></mat-progress-bar>
|
|
<span>{{ opSelectedWorkPackage.percentageDone }}%</span>
|
|
</div>
|
|
</div>
|
|
<div *ngIf="opSelectedWorkPackage.description" class="op-detail-description">
|
|
<span class="op-detail-label">Description</span>
|
|
<div class="op-description-content">{{ opSelectedWorkPackage.description }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="op-detail-footer">
|
|
<button mat-raised-button color="primary" (click)="openInOpenProject(opSelectedWorkPackage)">
|
|
<fa-icon icon="external-link-alt" class="mr-05"></fa-icon> Open in OpenProject
|
|
</button>
|
|
<button mat-stroked-button (click)="closeWorkPackageDetails()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Detail Loading Overlay -->
|
|
<div *ngIf="opDetailLoading" class="op-detail-overlay">
|
|
<div class="op-detail-loading">
|
|
<mat-spinner diameter="40"></mat-spinner>
|
|
<p>Fetching work item details...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
</div>
|