merging code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nitesh Yadav
2026-04-20 14:26:25 +05:30
co-authored by Claude Sonnet 4.6
322 changed files with 28779 additions and 4976 deletions
+16 -4
View File
@@ -50,7 +50,11 @@
<div class="rpt-detail">
<!-- Breadcrumb -->
<div class="rpt-detail__header">
<button mat-stroked-button class="rpt-back-btn" (click)="selectedSubCategory ? backToSubCategories() : clearCategory()">
<button
mat-stroked-button
class="rpt-back-btn"
(click)="selectedSubCategory ? backToSubCategories() : clearCategory()"
>
<i class="fas fa-arrow-left"></i>
{{ selectedSubCategory ? selectedCategory : 'All Categories' }}
</button>
@@ -65,7 +69,11 @@
<div class="rpt-search-row">
<mat-form-field appearance="outline" class="rpt-search">
<i class="fas fa-search rpt-search__icon" matPrefix></i>
<input matInput placeholder="Search in {{ selectedCategory }}..." (keyup)="applyFilter($event.target.value)" />
<input
matInput
placeholder="Search in {{ selectedCategory }}..."
(keyup)="applyFilter($event.target.value)"
/>
</mat-form-field>
</div>
@@ -124,9 +132,13 @@
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="rpt-row"
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
class="rpt-row"
[routerLink]="['/reports', 'run', row.reportName]"
[queryParams]="{ type: row.reportType, id: row.id }"></tr>
[queryParams]="{ type: row.reportType, id: row.id }"
></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
+111 -33
View File
@@ -60,10 +60,21 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
span {
border-radius: 2px;
&:nth-child(1) { background: $brand; }
&:nth-child(2) { background: $accent; }
&:nth-child(3) { background: #10b981; }
&:nth-child(4) { background: #cbd5e1; }
&:nth-child(1) {
background: $brand;
}
&:nth-child(2) {
background: $accent;
}
&:nth-child(3) {
background: #10b981;
}
&:nth-child(4) {
background: #cbd5e1;
}
}
}
@@ -83,21 +94,33 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
}
::ng-deep {
.mat-mdc-form-field-flex { height: 38px; }
.mat-mdc-form-field-flex {
height: 38px;
}
.mat-mdc-form-field-infix {
padding: 6px 0;
min-height: 38px;
}
.mat-mdc-form-field-subscript-wrapper { height: 0; min-height: 0; }
.mdc-notched-outline {
.mdc-notched-outline__leading { border-radius: 8px 0 0 8px; }
.mdc-notched-outline__trailing { border-radius: 0 8px 8px 0; }
.mat-mdc-form-field-subscript-wrapper {
height: 0;
min-height: 0;
}
input { font-size: 13px; }
.mdc-notched-outline {
.mdc-notched-outline__leading {
border-radius: 8px 0 0 8px;
}
.mdc-notched-outline__trailing {
border-radius: 0 8px 8px 0;
}
}
input {
font-size: 13px;
}
}
}
@@ -107,8 +130,13 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
@media (max-width: 900px) { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 500px) { grid-template-columns: 1fr; }
@media (width <= 900px) {
grid-template-columns: repeat(2, 1fr);
}
@media (width <= 500px) {
grid-template-columns: 1fr;
}
}
.rpt-tile {
@@ -117,13 +145,15 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
gap: 14px;
padding: 16px 18px;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.06);
border: 1px solid rgb(0 0 0 / 6%);
border-radius: 8px;
cursor: pointer;
transition: box-shadow 0.15s, transform 0.15s;
transition:
box-shadow 0.15s,
transform 0.15s;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
transform: translateY(-1px);
}
@@ -137,12 +167,35 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
font-size: 16px;
flex-shrink: 0;
&.blue { background: #e0f2fe; color: #0284c7; }
&.green { background: #d1fae5; color: #059669; }
&.purple { background: #ede9fe; color: #7c3aed; }
&.amber { background: #fef3c7; color: #d97706; }
&.teal { background: #ccfbf1; color: #0d9488; }
&.red { background: #fee2e2; color: #dc2626; }
&.blue {
background: #e0f2fe;
color: #0284c7;
}
&.green {
background: #d1fae5;
color: #059669;
}
&.purple {
background: #ede9fe;
color: #7c3aed;
}
&.amber {
background: #fef3c7;
color: #d97706;
}
&.teal {
background: #ccfbf1;
color: #0d9488;
}
&.red {
background: #fee2e2;
color: #dc2626;
}
}
&__info {
@@ -171,13 +224,17 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
transition: color 0.12s;
}
&:hover &__arrow { color: $brand; }
&:hover &__arrow {
color: $brand;
}
}
.rpt-tiles--sub {
grid-template-columns: repeat(2, 1fr);
@media (max-width: 600px) { grid-template-columns: 1fr; }
@media (width <= 600px) {
grid-template-columns: 1fr;
}
}
// Detail view
@@ -203,9 +260,14 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
border-color: $border !important;
color: $text-muted !important;
i { margin-right: 6px; }
i {
margin-right: 6px;
}
&:hover { color: $brand !important; border-color: $brand !important; }
&:hover {
color: $brand !important;
border-color: $brand !important;
}
}
// Table
@@ -245,8 +307,13 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
height: 38px;
border-bottom: 1px solid #f1f5f9;
&:nth-child(even) { background: #fafbfc; }
&:last-child { border-bottom: none; }
&:nth-child(even) {
background: #fafbfc;
}
&:last-child {
border-bottom: none;
}
}
.mat-mdc-cell {
@@ -294,11 +361,22 @@ $mono: 'Roboto Mono', 'DM Mono', monospace;
:host-context(.dark-theme) {
.rpt-tile {
background: #1e1e2e;
border-color: rgba(255, 255, 255, 0.06);
border-color: rgb(255 255 255 / 6%);
}
.rpt-tile__name { color: #e0e0e0; }
.rpt-tile__arrow { color: #444; }
.rpt-header__title { color: #e0e0e0; }
.rpt-detail__title { color: #e0e0e0; }
.rpt-tile__name {
color: #e0e0e0;
}
.rpt-tile__arrow {
color: #444;
}
.rpt-header__title {
color: #e0e0e0;
}
.rpt-detail__title {
color: #e0e0e0;
}
}
+67 -16
View File
@@ -45,10 +45,36 @@ const DEFAULT_CONFIG = { icon: 'fa-chart-bar', color: 'blue' };
/** Sub-category rules for large categories (keyword → sub-category name) */
const SUB_CATEGORY_RULES: Record<string, { name: string; keywords: string[]; icon: string; color: string }[]> = {
Loan: [
{ name: 'Disbursal & Approval', keywords: ['disbursal', 'pending approval', 'awaiting'], icon: 'fa-check-circle', color: 'green' },
{ name: 'Active & Portfolio', keywords: ['active loan', 'portfolio', 'outstanding', 'balance', 'summary'], icon: 'fa-chart-line', color: 'blue' },
{ name: 'Arrears & Aging', keywords: ['arrear', 'aging', 'overdue', 'delinquen', 'written off', 'write-off', 'obligation'], icon: 'fa-exclamation-triangle', color: 'red' },
{ name: 'Repayment & Collection', keywords: ['repayment', 'collection', 'demand', 'due', 'paid', 'payment', 'transaction'], icon: 'fa-money-bill', color: 'teal' },
{ name: 'Disbursal & Approval', keywords: [
'disbursal',
'pending approval',
'awaiting'
], icon: 'fa-check-circle', color: 'green' },
{ name: 'Active & Portfolio', keywords: [
'active loan',
'portfolio',
'outstanding',
'balance',
'summary'
], icon: 'fa-chart-line', color: 'blue' },
{ name: 'Arrears & Aging', keywords: [
'arrear',
'aging',
'overdue',
'delinquen',
'written off',
'write-off',
'obligation'
], icon: 'fa-exclamation-triangle', color: 'red' },
{ name: 'Repayment & Collection', keywords: [
'repayment',
'collection',
'demand',
'due',
'paid',
'payment',
'transaction'
], icon: 'fa-money-bill', color: 'teal' },
{ name: 'Other Loan Reports', keywords: [], icon: 'fa-file-alt', color: 'purple' }
]
};
@@ -89,7 +115,11 @@ export class ReportsComponent implements OnInit {
reportsData: any[] = [];
filter: string;
displayedColumns: string[] = ['reportName', 'reportType', 'reportCategory'];
displayedColumns: string[] = [
'reportName',
'reportType',
'reportCategory'
];
dataSource = new MatTableDataSource<any>();
/** Category dashboard */
@@ -136,10 +166,15 @@ export class ReportsComponent implements OnInit {
}
this.categories = Array.from(catMap.entries())
.sort((a, b) => b[1] - a[1])
.map(([name, count]) => {
const cfg = CATEGORY_CONFIG[name] || DEFAULT_CONFIG;
return { name, count, icon: cfg.icon, color: cfg.color };
});
.map(
([
name,
count
]) => {
const cfg = CATEGORY_CONFIG[name] || DEFAULT_CONFIG;
return { name, count, icon: cfg.icon, color: cfg.color };
}
);
}
selectCategory(category: string) {
@@ -173,19 +208,31 @@ export class ReportsComponent implements OnInit {
const matched = this.filteredReports.filter((r: any, idx: number) => {
if (assigned.has(idx)) return false;
const name = (r.reportName || '').toLowerCase();
return rule.keywords.some(kw => name.includes(kw));
return rule.keywords.some((kw) => name.includes(kw));
});
matched.forEach(r => assigned.add(this.filteredReports.indexOf(r)));
matched.forEach((r) => assigned.add(this.filteredReports.indexOf(r)));
if (matched.length > 0) {
this.subCategories.push({ name: rule.name, count: matched.length, icon: rule.icon, color: rule.color, reports: matched });
this.subCategories.push({
name: rule.name,
count: matched.length,
icon: rule.icon,
color: rule.color,
reports: matched
});
}
}
// "Other" bucket — remaining unmatched
const otherRule = rules.find(r => r.keywords.length === 0);
const otherRule = rules.find((r) => r.keywords.length === 0);
const remaining = this.filteredReports.filter((_, idx) => !assigned.has(idx));
if (remaining.length > 0 && otherRule) {
this.subCategories.push({ name: otherRule.name, count: remaining.length, icon: otherRule.icon, color: otherRule.color, reports: remaining });
this.subCategories.push({
name: otherRule.name,
count: remaining.length,
icon: otherRule.icon,
color: otherRule.color,
reports: remaining
});
}
}
@@ -219,7 +266,7 @@ export class ReportsComponent implements OnInit {
this.isSearching = true;
this.showSubCategories = false;
if (this.selectedSubCategory) {
const subCat = this.subCategories.find(sc => sc.name === this.selectedSubCategory);
const subCat = this.subCategories.find((sc) => sc.name === this.selectedSubCategory);
this.setTableData(subCat ? subCat.reports : this.filteredReports);
} else if (this.selectedCategory) {
this.setTableData(this.filteredReports);
@@ -230,7 +277,11 @@ export class ReportsComponent implements OnInit {
} else {
if (this.selectedSubCategory) {
// keep showing sub-cat table
} else if (this.selectedCategory && SUB_CATEGORY_RULES[this.selectedCategory] && this.filteredReports.length > 15) {
} else if (
this.selectedCategory &&
SUB_CATEGORY_RULES[this.selectedCategory] &&
this.filteredReports.length > 15
) {
this.isSearching = false;
this.showSubCategories = true;
} else {
@@ -1,3 +1,11 @@
<!--
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="container">
<div class="page-header">
<fa-icon icon="shield-alt" class="header-icon"></fa-icon>
@@ -12,11 +20,7 @@
</mat-form-field>
<div class="filter-chips">
<button
mat-stroked-button
[class.active-filter]="activeFilter === null"
(click)="filterByResult(null)"
>
<button mat-stroked-button [class.active-filter]="activeFilter === null" (click)="filterByResult(null)">
<fa-icon icon="list" class="chip-icon"></fa-icon> All
</button>
<button