/** * 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/. */ import { Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { SharesService } from '../../shares.service'; import { shareReportCsv, shareReportPrint } from '../share-report.util'; interface AreaRow { area: string; memberCount: number; shares: number; shareValue: number; } interface AreaMemberRow { clientId: number; cifNumber: string; clientName: string; mobileNo: string; officeName: string; area: string; postalCode: string; sharesHeld: number; shareValue: number; } /** * Area Wise — where the society's members and its share capital actually sit. * *

* A co-operative's area of operation is part of its licence, so "how much of our capital comes from * which area" is a question both the board and the Registrar ask. Grouped on the member's own * recorded address rather than the branch that serves them: the two answer different questions, and * a licence is written against the address. * *

* Members with no address are their own group rather than dropped — an unaddressable member is a gap * in the register worth seeing. */ @Component({ selector: 'mifosx-share-area-wise', templateUrl: './share-area-wise.component.html', styleUrls: [ '../../../letter-mgmt/certificates/certificates.shared.scss', '../share-report.shared.scss' ], imports: [ CommonModule, FormsModule, FaIconComponent ] }) export class ShareAreaWiseComponent implements OnInit { private sharesService = inject(SharesService); private router = inject(Router); loading = true; loadError = ''; areas: AreaRow[] = []; members: AreaMemberRow[] = []; totals: any = null; search = ''; /** Set by clicking an area row; '' means every area. */ areaFilter = ''; ngOnInit(): void { this.load(); } load(): void { this.loading = true; this.loadError = ''; this.sharesService .getShareAreaWise({}) .pipe( catchError((err) => { this.loadError = this.readError(err, 'The area-wise report could not be loaded.'); return of(null); }) ) .subscribe((res: any) => { this.areas = res?.areas || []; this.members = res?.members || []; this.totals = res?.totals || null; this.loading = false; }); } get filteredMembers(): AreaMemberRow[] { const term = this.search.trim().toLowerCase(); return this.members.filter((m) => { if (this.areaFilter && m.area !== this.areaFilter) { return false; } if (!term) { return true; } return [ m.clientName, m.cifNumber, m.area, m.postalCode ] .filter(Boolean) .some((field) => String(field).toLowerCase().includes(term)); }); } /** An area's share of the society's capital — the reading this report exists for. */ capitalShare(row: AreaRow): number { const total = Number(this.totals?.shareValue || 0); return total ? (Number(row.shareValue || 0) * 100) / total : 0; } barWidth(row: AreaRow): number { const largest = Math.max(...this.areas.map((a) => Number(a.shareValue || 0)), 1); return (Number(row.shareValue || 0) * 100) / largest; } selectArea(area: string): void { this.areaFilter = this.areaFilter === area ? '' : area; } openMember(row: AreaMemberRow): void { this.router.navigate([ '/clients', row.clientId, 'general' ]); } clearFilters(): void { this.search = ''; this.areaFilter = ''; } exportCsv(): void { shareReportCsv( 'share-area-wise', [ 'Area', 'Member', 'Membership No', 'Mobile', 'Branch', 'Postal Code', 'Shares Held', 'Share Value' ], this.filteredMembers.map((m) => [ m.area, m.clientName, m.cifNumber, m.mobileNo, m.officeName, m.postalCode, m.sharesHeld, m.shareValue ]) ); } print(): void { shareReportPrint(); } private readError(err: any, fallback: string): string { return ( err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || err?.error?.developerMessage || fallback ); } }