feat(cfcfrms): I4C Live screens for the I4C-MHA integration
New CFCFRMS > I4C Live section backed by /v1/i4c/admin and /v1/i4c/cases (the demo mock interceptor does not match these paths): - Dashboard, complaints from I4C, holds register, approvals, responses and API log, bank code master, intimation settings. - Hold popup: overview, hold transactions, customer intimation (automatic and manual), maker request (full / partial release, freeze, unfreeze), GRM / MRM and documents, request history. - GRM grievances, MRM restorations and MRM balance report pages; shared date range filter (today, yesterday, week, month, custom), server-side paging and filters on every list; document upload / download. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<!--
|
||||
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="date-range" role="group" [attr.aria-label]="label + ' filter'">
|
||||
<span class="date-range__label"><fa-icon icon="calendar-alt"></fa-icon> {{ label }}</span>
|
||||
<div class="date-range__presets">
|
||||
@for (p of presets; track p.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="date-chip"
|
||||
[class.date-chip--active]="range.preset === p.value"
|
||||
[attr.aria-pressed]="range.preset === p.value"
|
||||
(click)="pick(p.value)"
|
||||
>
|
||||
{{ p.label }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@if (range.preset !== 'ALL') {
|
||||
<div class="date-range__custom">
|
||||
<label>
|
||||
From
|
||||
<input
|
||||
type="date"
|
||||
[max]="range.toDate || today"
|
||||
[(ngModel)]="range.fromDate"
|
||||
(change)="customChanged()"
|
||||
[attr.aria-label]="label + ' from'"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
To
|
||||
<input
|
||||
type="date"
|
||||
[min]="range.fromDate"
|
||||
[max]="today"
|
||||
[(ngModel)]="range.toDate"
|
||||
(change)="customChanged()"
|
||||
[attr.aria-label]="label + ' to'"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 12px;
|
||||
|
||||
&__label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__presets {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
padding: 3px;
|
||||
border-radius: 999px;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
&__custom {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 8px;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
color: #0f172a;
|
||||
background: #fff;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid #2563eb;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date-chip {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
border-radius: 999px;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid #2563eb;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
|
||||
export type I4cDatePreset = 'ALL' | 'TODAY' | 'YESTERDAY' | 'WEEK' | 'MONTH' | 'CUSTOM';
|
||||
|
||||
/** A date range as the list APIs take it: yyyy-MM-dd, both days included, empty for "no limit". */
|
||||
export interface I4cDateRange {
|
||||
preset: I4cDatePreset;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
}
|
||||
|
||||
export const ALL_DATES: I4cDateRange = { preset: 'ALL', fromDate: '', toDate: '' };
|
||||
|
||||
/** yyyy-MM-dd in the browser's own time zone (toISOString would shift to UTC). */
|
||||
function iso(d: Date): string {
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
}
|
||||
|
||||
/** The days a preset stands for, worked out from today. */
|
||||
export function presetRange(preset: I4cDatePreset): I4cDateRange {
|
||||
const today = new Date();
|
||||
const day = (offset: number) => new Date(today.getFullYear(), today.getMonth(), today.getDate() + offset);
|
||||
switch (preset) {
|
||||
case 'TODAY':
|
||||
return { preset, fromDate: iso(today), toDate: iso(today) };
|
||||
case 'YESTERDAY':
|
||||
return { preset, fromDate: iso(day(-1)), toDate: iso(day(-1)) };
|
||||
case 'WEEK': {
|
||||
// The week starts on Monday.
|
||||
const sinceMonday = (today.getDay() + 6) % 7;
|
||||
return { preset, fromDate: iso(day(-sinceMonday)), toDate: iso(today) };
|
||||
}
|
||||
case 'MONTH':
|
||||
return { preset, fromDate: iso(new Date(today.getFullYear(), today.getMonth(), 1)), toDate: iso(today) };
|
||||
default:
|
||||
return { ...ALL_DATES };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Date filter shared by the I4C lists: All, Today, Yesterday, This Week, This Month, or a custom From / To. Emits the
|
||||
* new range whenever it changes; the page then reloads from page one.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-i4c-date-range',
|
||||
templateUrl: './i4c-date-range.component.html',
|
||||
styleUrls: ['./i4c-date-range.component.scss'],
|
||||
imports: [
|
||||
FormsModule,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class I4cDateRangeComponent {
|
||||
@Input() label = 'Date';
|
||||
@Input() range: I4cDateRange = { ...ALL_DATES };
|
||||
@Output() rangeChange = new EventEmitter<I4cDateRange>();
|
||||
|
||||
readonly presets: { value: I4cDatePreset; label: string }[] = [
|
||||
{ value: 'ALL', label: 'All' },
|
||||
{ value: 'TODAY', label: 'Today' },
|
||||
{ value: 'YESTERDAY', label: 'Yesterday' },
|
||||
{ value: 'WEEK', label: 'This Week' },
|
||||
{ value: 'MONTH', label: 'This Month' },
|
||||
{ value: 'CUSTOM', label: 'Custom' }
|
||||
];
|
||||
|
||||
readonly today = iso(new Date());
|
||||
|
||||
pick(preset: I4cDatePreset): void {
|
||||
if (preset === 'CUSTOM') {
|
||||
// Keep the days already shown so the officer can adjust them.
|
||||
this.range = { ...this.range, preset };
|
||||
return;
|
||||
}
|
||||
this.emit(presetRange(preset));
|
||||
}
|
||||
|
||||
customChanged(): void {
|
||||
const { fromDate, toDate } = this.range;
|
||||
if (fromDate && toDate && toDate < fromDate) {
|
||||
// A reversed range would match nothing; read it the way it was meant.
|
||||
this.range = { preset: 'CUSTOM', fromDate: toDate, toDate: fromDate };
|
||||
}
|
||||
this.emit({ ...this.range, preset: 'CUSTOM' });
|
||||
}
|
||||
|
||||
private emit(range: I4cDateRange): void {
|
||||
this.range = range;
|
||||
this.rangeChange.emit(range);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user