feat: non-individual entity UI improvements — hide individual-only sections, fix legal form display, send extended fields to API

- Hide Family Members tab/section for non-individual entities in client view, create flow, and preview
- Hide Financial & Classification section for non-individual entities in personal data and preview
- Fix legal form column in clients list — fetch legalForm from client API for correct display
- Add person/business icon before client name in list rows
- Remove unsupportedNpd filter — send authorizedSignatory, additionalInfo, dateOfRegistration to API
- Add registration number format hint (mat-hint) based on constitution type
- Add spacing between verification row and constitution type dropdowns
- Reduce table cell padding and prevent action buttons from being clipped
- Add missing MPL-2.0 license headers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-04 15:10:51 +05:30
co-authored by Claude Opus 4.6
parent ab01111424
commit b890d24bb7
522 changed files with 51991 additions and 21885 deletions
@@ -6,23 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_ADHOC'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create AdHoc Query' | translate }}
</button>
</div>
<mat-card class="container">
<!-- Header Bar -->
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-chevron-right"></i>
{{ 'labels.heading.AdHocQuery' | translate }}
</h2>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<input matInput placeholder="{{ 'labels.inputs.Filter' | translate }}" (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" [routerLink]="['create']" class="btn-create" *mifosxHasPermission="'CREATE_ADHOC'">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Create AdHoc Query' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8 table-container">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Table -->
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let adhocquery">{{ adhocquery.name }}</td>
@@ -70,9 +81,9 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</mat-card>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,115 +6,114 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="caq-container">
<!-- Header -->
<div class="caq-header">
<button mat-stroked-button [routerLink]="['../']" class="caq-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.AdHocQuery' | translate }}
</button>
<h2 class="caq-header__title">{{ 'labels.buttons.Create AdHoc Query' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="caq-card">
<form [formGroup]="adhocQueryForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<!-- Query Details Section -->
<div class="caq-section">
<div class="caq-section__header">
<i class="fas fa-database"></i>
<h3>Query Details</h3>
</div>
<div class="caq-grid">
<mat-form-field appearance="outline" class="caq-field">
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (adhocQueryForm.controls.name.hasError('required')) {
<mat-error>
{{ 'labels.inputs.name' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error>{{ 'labels.inputs.name' | translate }} {{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong></mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.SQL Query' | translate }}</mat-label>
<textarea matInput required formControlName="query"></textarea>
@if (adhocQueryForm.controls.query.hasError('required')) {
<mat-error>
{{ 'labels.inputs.SQL Query' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="caq-field">
<mat-label>{{ 'labels.inputs.Insert into table' | translate }}</mat-label>
<input matInput required formControlName="tableName" />
@if (adhocQueryForm.controls.tableName.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Insert into table' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error>{{ 'labels.inputs.Insert into table' | translate }} {{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong></mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="caq-field caq-field--full">
<mat-label>{{ 'labels.inputs.SQL Query' | translate }}</mat-label>
<textarea matInput required formControlName="query" rows="3"></textarea>
@if (adhocQueryForm.controls.query.hasError('required')) {
<mat-error>{{ 'labels.inputs.SQL Query' | translate }} {{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong></mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="caq-field caq-field--full">
<mat-label>{{ 'labels.inputs.Table Fields' | translate }}</mat-label>
<textarea matInput required formControlName="tableFields"></textarea>
<textarea matInput required formControlName="tableFields" rows="2"></textarea>
@if (adhocQueryForm.controls.tableFields.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Table Fields' | translate }} {{ 'labels.commons.are' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error>{{ 'labels.inputs.Table Fields' | translate }} {{ 'labels.commons.are' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong></mat-error>
}
</mat-form-field>
</div>
</div>
<mat-form-field>
<!-- Notification & Schedule Section -->
<div class="caq-section">
<div class="caq-section__header">
<i class="fas fa-clock"></i>
<h3>Notification & Schedule</h3>
</div>
<div class="caq-grid">
<mat-form-field appearance="outline" class="caq-field">
<mat-label>{{ 'labels.inputs.Email' | translate }}</mat-label>
<input matInput formControlName="email" />
@if (adhocQueryForm.controls.email.hasError('email')) {
<mat-error>
{{ 'labels.inputs.Email' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.invalid' | translate }}</strong>
</mat-error>
<mat-error>{{ 'labels.inputs.Email' | translate }} {{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.invalid' | translate }}</strong></mat-error>
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="caq-field">
<mat-label>{{ 'labels.inputs.Report Run Frequency' | translate }}</mat-label>
<mat-select formControlName="reportRunFrequency">
@for (reportRunFrequency of reportRunFrequencyData; track reportRunFrequency) {
<mat-option [value]="reportRunFrequency.id">
{{ reportRunFrequency.value }}
</mat-option>
<mat-option [value]="reportRunFrequency.id">{{ reportRunFrequency.value }}</mat-option>
}
</mat-select>
</mat-form-field>
@if (adhocQueryForm.controls.reportRunFrequency.value === 5) {
<mat-form-field>
<mat-form-field appearance="outline" class="caq-field">
<mat-label>{{ 'labels.inputs.Custom Report Run Frequency (Days)' | translate }}</mat-label>
<input type="number" matInput required formControlName="reportRunEvery" />
@if (adhocQueryForm.controls.reportRunEvery.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Custom Report Run Frequency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
<mat-error>{{ 'labels.inputs.Custom Report Run Frequency' | translate }} {{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong></mat-error>
}
@if (adhocQueryForm.controls.reportRunEvery.hasError('min')) {
<mat-error>
{{ 'labels.inputs.Custom Report Run Frequency' | translate }}
<strong>{{ 'labels.commons.must be larger than 0' | translate }}</strong>
</mat-error>
<mat-error>{{ 'labels.inputs.Custom Report Run Frequency' | translate }} <strong>{{ 'labels.commons.must be larger than 0' | translate }}</strong></mat-error>
}
</mat-form-field>
}
<mat-checkbox labelPosition="before" formControlName="isActive">
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
<div class="caq-checkbox">
<mat-checkbox labelPosition="after" formControlName="isActive">
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
</div>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<!-- Actions -->
<div class="caq-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="caq-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
color="primary"
[disabled]="!adhocQueryForm.valid"
*mifosxHasPermission="'CREATE_ADHOC'"
>
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="!adhocQueryForm.valid"
*mifosxHasPermission="'CREATE_ADHOC'" class="caq-actions__submit">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,56 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.caq-container { max-width: 800px; margin: 0 auto; padding: 20px 16px; }
.caq-header {
display: flex; align-items: center; gap: 14px; margin-bottom: 20px;
&__title { font-size: 18px; font-weight: 600; color: $gray-900; margin: 0; }
}
.caq-back {
border-radius: 8px !important; font-size: 12px !important; font-weight: 600 !important;
height: 32px !important; border-color: $border !important; color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.caq-card { background: #fff; border: 1px solid $border; border-radius: 10px; overflow: hidden; }
.caq-section {
&__header {
display: flex; align-items: center; gap: 10px; padding: 12px 24px;
background: $gray-50; border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
& + & { border-top: 1px solid $gray-200; }
}
.caq-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 4px 16px; padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.caq-field {
width: 100%;
&--full { grid-column: 1 / -1; }
}
.caq-checkbox { grid-column: 1 / -1; padding: 8px 0; }
.caq-actions {
display: flex; justify-content: flex-end; gap: 10px; padding: 14px 24px;
border-top: 1px solid $gray-200; background: $gray-50;
&__cancel { border-radius: 8px !important; font-size: 13px !important; font-weight: 600 !important; height: 36px !important; border-color: $border !important; color: $gray-700 !important; }
&__submit { border-radius: 8px !important; font-size: 13px !important; font-weight: 600 !important; height: 36px !important; box-shadow: 0 1px 3px rgba($primary, 0.3) !important; i { margin-right: 6px; font-size: 12px; } }
}
@@ -6,347 +6,186 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<!-- TODO: Refactor Permissions -->
<div class="container">
<mat-card>
<div class="layout-row responsive-column gap-20px">
<div class="flex-50">
<mat-nav-list>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Offices']">
<fa-icon icon="building" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Offices']">{{ 'labels.heading.Offices' | translate }}</h4>
@if (!arrowBooleans[0]) {
<fa-icon (click)="arrowBooleansToggle(0)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[0]) {
<fa-icon (click)="arrowBooleansToggle(0)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[0]) {
<p matLine [routerLink]="['Offices']">
{{ 'labels.text.Download, Upload Offices Template' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Users']">
<fa-icon icon="user" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Users']">{{ 'labels.heading.Users' | translate }}</h4>
@if (!arrowBooleans[1]) {
<fa-icon (click)="arrowBooleansToggle(1)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[1]) {
<fa-icon (click)="arrowBooleansToggle(1)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[1]) {
<p matLine [routerLink]="['Users']">
{{ 'labels.text.Download offices template and Upload office excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Groups']">
<fa-icon icon="users" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Groups']">{{ 'labels.heading.Groups' | translate }}</h4>
@if (!arrowBooleans[2]) {
<fa-icon (click)="arrowBooleansToggle(2)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[2]) {
<fa-icon (click)="arrowBooleansToggle(2)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[2]) {
<p matLine [routerLink]="['Groups']">
{{ 'labels.text.Download groups template and Upload group excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Loan Accounts']">
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Loan Accounts']">{{ 'labels.heading.Loan Accounts' | translate }}</h4>
@if (!arrowBooleans[3]) {
<fa-icon (click)="arrowBooleansToggle(3)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[3]) {
<fa-icon (click)="arrowBooleansToggle(3)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[3]) {
<p matLine [routerLink]="['Loan Accounts']">
{{ 'labels.text.Download loan accounts template and upload loan account excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Savings Accounts']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Savings Accounts']">{{ 'labels.heading.Savings Accounts' | translate }}</h4>
@if (!arrowBooleans[4]) {
<fa-icon (click)="arrowBooleansToggle(4)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[4]) {
<fa-icon (click)="arrowBooleansToggle(4)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[4]) {
<p matLine [routerLink]="['Savings Accounts']">
{{
'labels.text.Download savings accounts template and upload savings account excel files' | translate
}}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Fixed Deposit Accounts']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Fixed Deposit Accounts']">
{{ 'labels.heading.Fixed Deposit Accounts' | translate }}
</h4>
@if (!arrowBooleans[5]) {
<fa-icon (click)="arrowBooleansToggle(5)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[5]) {
<fa-icon (click)="arrowBooleansToggle(5)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[5]) {
<p matLine [routerLink]="['Fixed Deposit Accounts']">
{{
'labels.text.Download fixed deposit accounts template and upload fixed deposit account excel files'
| translate
}}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Chart of Accounts']">
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Chart of Accounts']">{{ 'labels.heading.Chart of Accounts' | translate }}</h4>
@if (!arrowBooleans[6]) {
<fa-icon (click)="arrowBooleansToggle(6)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[6]) {
<fa-icon (click)="arrowBooleansToggle(6)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[6]) {
<p matLine [routerLink]="['Chart of Accounts']">
{{
'labels.text.Download chart of accounts template and upload chart of account excel files' | translate
}}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Share Accounts']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Share Accounts']">{{ 'labels.heading.Share Accounts' | translate }}</h4>
@if (!arrowBooleans[7]) {
<fa-icon (click)="arrowBooleansToggle(7)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[7]) {
<fa-icon (click)="arrowBooleansToggle(7)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[7]) {
<p matLine [routerLink]="['Share Accounts']">
{{ 'labels.text.Download share accounts template and upload share account excel files' | translate }}
</p>
}
</mat-list-item>
</mat-nav-list>
</div>
<div class="flex-50">
<mat-nav-list>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Employees']">
<fa-icon icon="user" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Employees']">{{ 'labels.heading.Employees' | translate }}</h4>
@if (!arrowBooleans[8]) {
<fa-icon (click)="arrowBooleansToggle(8)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[8]) {
<fa-icon (click)="arrowBooleansToggle(8)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[8]) {
<p matLine [routerLink]="['Employees']">
{{ 'labels.text.Download loan accounts template and upload loan account excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Clients']">
<fa-icon icon="user" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Clients']">{{ 'labels.heading.Clients' | translate }}</h4>
@if (!arrowBooleans[9]) {
<fa-icon (click)="arrowBooleansToggle(9)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[9]) {
<fa-icon (click)="arrowBooleansToggle(9)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[9]) {
<p matLine [routerLink]="['Clients']">
{{ 'labels.text.Download clients template and upload clients excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Centers']">
<fa-icon icon="users" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Centers']">{{ 'labels.heading.Centers' | translate }}</h4>
@if (!arrowBooleans[10]) {
<fa-icon (click)="arrowBooleansToggle(10)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[10]) {
<fa-icon (click)="arrowBooleansToggle(10)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[10]) {
<p matLine [routerLink]="['Centers']">
{{ 'labels.text.Download centers template and upload centers excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Loan Repayments']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Loan Repayments']">{{ 'labels.heading.Loan Repayments' | translate }}</h4>
@if (!arrowBooleans[11]) {
<fa-icon (click)="arrowBooleansToggle(11)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[11]) {
<fa-icon (click)="arrowBooleansToggle(11)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[11]) {
<p matLine [routerLink]="['Loan Repayments']">
{{ 'labels.text.Download loan repayments template and upload loan repayment excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Savings Transactions']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Savings Transactions']">
{{ 'labels.heading.Savings Transactions' | translate }}
</h4>
@if (!arrowBooleans[12]) {
<fa-icon (click)="arrowBooleansToggle(12)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[12]) {
<fa-icon (click)="arrowBooleansToggle(12)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[12]) {
<p matLine [routerLink]="['Savings Transactions']">
{{
'labels.text.Download savings transactions template and upload savings transaction excel files'
| translate
}}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Fixed Deposit Transactions']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Fixed Deposit Transactions']">
{{ 'labels.heading.Fixed Deposit Transactions' | translate }}
</h4>
@if (!arrowBooleans[13]) {
<fa-icon (click)="arrowBooleansToggle(13)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[13]) {
<fa-icon (click)="arrowBooleansToggle(13)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[13]) {
<p matLine [routerLink]="['Fixed Deposit Transactions']">
{{
'labels.text.Download fixed deposit transactions template and upload fixed deposit transaction excel files'
| translate
}}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Recurring Deposit Transactions']">
<fa-icon icon="briefcase" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Recurring Deposit Transactions']">
{{ 'labels.heading.Recurring Deposit Transactions' | translate }}
</h4>
@if (!arrowBooleans[14]) {
<fa-icon (click)="arrowBooleansToggle(14)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[14]) {
<fa-icon (click)="arrowBooleansToggle(14)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[14]) {
<p matLine [routerLink]="['Recurring Deposit Transactions']">
{{
'labels.text.Download recurring deposit transactions template and upload recurring deposit transaction excel files'
| translate
}}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Journal Entries']">
<fa-icon icon="chevron-right" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Journal Entries']">{{ 'labels.heading.Journal Entries' | translate }}</h4>
@if (!arrowBooleans[15]) {
<fa-icon (click)="arrowBooleansToggle(15)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[15]) {
<fa-icon (click)="arrowBooleansToggle(15)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[15]) {
<p matLine [routerLink]="['Journal Entries']">
{{ 'labels.text.Download journal entries template and upload journal entries excel files' | translate }}
</p>
}
</mat-list-item>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<mat-icon matListIcon [routerLink]="['Guarantors']">
<fa-icon icon="chevron-right" size="sm"></fa-icon>
</mat-icon>
<h4 matLine [routerLink]="['Guarantors']">{{ 'labels.heading.Guarantors' | translate }}</h4>
@if (!arrowBooleans[16]) {
<fa-icon (click)="arrowBooleansToggle(16)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[16]) {
<fa-icon (click)="arrowBooleansToggle(16)" icon="arrow-up" size="md"></fa-icon>
}
@if (arrowBooleans[16]) {
<p matLine [routerLink]="['Guarantors']">
{{ 'labels.text.Download guarantors template and upload guarantor excel files' | translate }}
</p>
}
</mat-list-item>
</mat-nav-list>
</div>
<div class="org-container">
<div class="org-header">
<div class="org-header__icon"><i class="fas fa-upload"></i></div>
<div>
<h1 class="org-header__title">{{ 'labels.heading.Bulk Import' | translate }}</h1>
<p class="org-header__sub">Download templates and upload excel files for bulk data import</p>
</div>
</mat-card>
</div>
<div class="org-grid">
<!-- Offices -->
<a class="org-card" [routerLink]="['Offices']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--blue"><i class="fas fa-building"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Offices' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download, Upload Offices Template' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Users -->
<a class="org-card" [routerLink]="['Users']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--purple"><i class="fas fa-user"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Users' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download offices template and Upload office excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Groups -->
<a class="org-card" [routerLink]="['Groups']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--green"><i class="fas fa-users"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Groups' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download groups template and Upload group excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Loan Accounts -->
<a class="org-card" [routerLink]="['Loan Accounts']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--orange"><i class="fas fa-money-bill-alt"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Loan Accounts' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download loan accounts template and upload loan account excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Savings Accounts -->
<a class="org-card" [routerLink]="['Savings Accounts']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--emerald"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Savings Accounts' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download savings accounts template and upload savings account excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Fixed Deposit Accounts -->
<a class="org-card" [routerLink]="['Fixed Deposit Accounts']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--teal"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Fixed Deposit Accounts' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download fixed deposit accounts template and upload fixed deposit account excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Chart of Accounts -->
<a class="org-card" [routerLink]="['Chart of Accounts']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--amber"><i class="fas fa-money-bill-alt"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Chart of Accounts' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download chart of accounts template and upload chart of account excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Share Accounts -->
<a class="org-card" [routerLink]="['Share Accounts']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--indigo"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Share Accounts' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download share accounts template and upload share account excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Employees -->
<a class="org-card" [routerLink]="['Employees']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--purple"><i class="fas fa-user"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Employees' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download loan accounts template and upload loan account excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Clients -->
<a class="org-card" [routerLink]="['Clients']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--cyan"><i class="fas fa-user"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Clients' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download clients template and upload clients excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Centers -->
<a class="org-card" [routerLink]="['Centers']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--green"><i class="fas fa-users"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Centers' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download centers template and upload centers excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Loan Repayments -->
<a class="org-card" [routerLink]="['Loan Repayments']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--rose"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Loan Repayments' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download loan repayments template and upload loan repayment excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Savings Transactions -->
<a class="org-card" [routerLink]="['Savings Transactions']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--emerald"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Savings Transactions' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download savings transactions template and upload savings transaction excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Fixed Deposit Transactions -->
<a class="org-card" [routerLink]="['Fixed Deposit Transactions']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--teal"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Fixed Deposit Transactions' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download fixed deposit transactions template and upload fixed deposit transaction excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Recurring Deposit Transactions -->
<a class="org-card" [routerLink]="['Recurring Deposit Transactions']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--amber"><i class="fas fa-briefcase"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Recurring Deposit Transactions' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download recurring deposit transactions template and upload recurring deposit transaction excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Journal Entries -->
<a class="org-card" [routerLink]="['Journal Entries']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--slate"><i class="fas fa-chevron-right"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Journal Entries' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download journal entries template and upload journal entries excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
<!-- Guarantors -->
<a class="org-card" [routerLink]="['Guarantors']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--red"><i class="fas fa-chevron-right"></i></div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Guarantors' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Download guarantors template and upload guarantor excel files' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
</div>
@@ -5,3 +5,212 @@
* 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/.
*/
$border: #e2e8f0;
$gray-50: #f8fafc;
$gray-100: #f1f5f9;
$gray-400: #94a3b8;
$gray-500: #64748b;
$gray-700: #334155;
$gray-900: #0f172a;
// ── Container ──
.org-container {
max-width: 1100px;
margin: 0 auto;
padding: 24px 20px;
}
// ── Page Header ──
.org-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 28px;
&__icon {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, #0d3d78, #1a5276);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 18px;
flex-shrink: 0;
}
&__title {
font-size: 20px;
font-weight: 700;
color: $gray-900;
margin: 0;
letter-spacing: -0.3px;
}
&__sub {
font-size: 13px;
color: $gray-500;
margin: 2px 0 0;
}
}
// ── Card Grid ──
.org-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}
// ── Individual Card ──
.org-card {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 16px;
background: #fff;
border: 1px solid $border;
border-radius: 10px;
text-decoration: none;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
border-color: #cbd5e1;
background: $gray-50;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
.org-card__arrow {
color: $gray-700;
transform: translateX(2px);
}
}
&__icon {
width: 40px;
height: 40px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
flex-shrink: 0;
&--blue {
background: rgba(59, 130, 246, 0.1);
color: #2563eb;
}
&--amber {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
&--green {
background: rgba(34, 197, 94, 0.1);
color: #16a34a;
}
&--emerald {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
&--purple {
background: rgba(139, 92, 246, 0.1);
color: #7c3aed;
}
&--indigo {
background: rgba(99, 102, 241, 0.1);
color: #4f46e5;
}
&--slate {
background: rgba(100, 116, 139, 0.1);
color: #475569;
}
&--orange {
background: rgba(249, 115, 22, 0.1);
color: #ea580c;
}
&--teal {
background: rgba(20, 184, 166, 0.1);
color: #0d9488;
}
&--cyan {
background: rgba(6, 182, 212, 0.1);
color: #0891b2;
}
&--rose {
background: rgba(244, 63, 94, 0.1);
color: #e11d48;
}
&--red {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
}
&__body {
flex: 1;
min-width: 0;
}
&__title {
font-size: 13.5px;
font-weight: 600;
color: $gray-900;
margin: 0;
line-height: 1.3;
}
&__desc {
font-size: 11.5px;
color: $gray-400;
margin: 3px 0 0;
line-height: 1.35;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__arrow {
font-size: 11px;
color: #cbd5e1;
flex-shrink: 0;
transition: all 0.15s ease;
}
}
// ── Dark mode ──
:host-context(.dark-theme) {
.org-header {
&__title { color: #e2e8f0; }
&__sub { color: #94a3b8; }
}
.org-card {
background: #1e1e1e;
border-color: #333;
&:hover {
background: #262626;
border-color: #444;
}
&__title { color: #e2e8f0; }
&__desc { color: #64748b; }
&__arrow { color: #444; }
}
}
@@ -8,10 +8,6 @@
/** Angular Imports */
import { Component } from '@angular/core';
import { MatNavList, MatListItem } from '@angular/material/list';
import { MatIcon } from '@angular/material/icon';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatLine } from '@angular/material/grid-list';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -22,27 +18,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './bulk-import.component.html',
styleUrls: ['./bulk-import.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatNavList,
MatListItem,
MatIcon,
FaIconComponent,
MatLine
...STANDALONE_SHARED_IMPORTS
]
})
export class BulkImportComponent {
// Initialize an array of 17 boolean values, all set to false
arrowBooleans: boolean[] = new Array(17).fill(false);
constructor() {}
/**
* Popover function
* @param arrowNumber - The index of the boolean value to toggle.
*/
arrowBooleansToggle(arrowNumber: number) {
// Toggle the boolean value at the given index
this.arrowBooleans[arrowNumber] = !this.arrowBooleans[arrowNumber];
}
}
export class BulkImportComponent {}
@@ -6,18 +6,28 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="bl-container">
<div class="bl-header">
<button mat-stroked-button [routerLink]="['../']" class="bl-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
</button>
<h2 class="bl-header__title">Bulk Loan Reassignment</h2>
</div>
<div class="bl-card">
<form [formGroup]="bulkLoanForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<!-- Section 1: Assignment Details -->
<div class="bl-section">
<div class="bl-section__header">
<i class="fas fa-exchange-alt"></i>
<h3>Assignment Details</h3>
</div>
<div class="bl-grid">
<mat-form-field appearance="outline" class="bl-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="officeId" (selectionChange)="getOffice($event.value)">
@for (office of offices; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
<mat-option [value]="office.id">{{ office.name }}</mat-option>
}
</mat-select>
@if (bulkLoanForm.controls.officeId.hasError('required')) {
@@ -27,10 +37,8 @@
</mat-error>
}
</mat-form-field>
</div>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48" (click)="assignmentDatePicker.open()">
<mat-form-field appearance="outline" class="bl-field" (click)="assignmentDatePicker.open()">
<mat-label>{{ 'labels.inputs.Assignment Date' | translate }}</mat-label>
<input
matInput
@@ -50,11 +58,9 @@
</mat-error>
}
</mat-form-field>
</div>
<div class="layout-row-wrap gap-2px responsive-column">
@if (fromLoanOfficers !== undefined) {
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="bl-field">
<mat-label>{{ 'labels.inputs.From loan officer' | translate }}</mat-label>
<mat-select
required
@@ -62,9 +68,7 @@
(selectionChange)="getFromOfficers($event.value)"
>
@for (fromOfficers of fromLoanOfficers; track fromOfficers) {
<mat-option [value]="fromOfficers.id">
{{ fromOfficers.displayName }}
</mat-option>
<mat-option [value]="fromOfficers.id">{{ fromOfficers.displayName }}</mat-option>
}
</mat-select>
@if (bulkLoanForm.controls.fromLoanOfficerId.hasError('required')) {
@@ -75,16 +79,12 @@
}
</mat-form-field>
}
</div>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="bl-field">
<mat-label>{{ 'labels.inputs.To loan officer' | translate }}</mat-label>
<mat-select required formControlName="toLoanOfficerId">
@for (toOfficers of toLoanOfficers; track toOfficers) {
<mat-option [value]="toOfficers.id">
{{ toOfficers.displayName }}
</mat-option>
<mat-option [value]="toOfficers.id">{{ toOfficers.displayName }}</mat-option>
}
</mat-select>
@if (bulkLoanForm.controls.toLoanOfficerId.hasError('required')) {
@@ -95,84 +95,71 @@
}
</mat-form-field>
</div>
</div>
<div class="layout-row-wrap gap-2px responsive-column">
@if (officerTemplate !== undefined) {
<table class="flex-48">
<thead>
<th>{{ 'labels.inputs.Clients' | translate }}</th>
</thead>
<tbody>
<!-- Section 2: Loan Selection -->
@if (officerTemplate !== undefined) {
<div class="bl-section">
<div class="bl-section__header">
<i class="fas fa-list-check"></i>
<h3>Loan Selection</h3>
</div>
<div class="bl-tables">
<div class="bl-loan-table">
<div class="bl-loan-table__header">{{ 'labels.inputs.Clients' | translate }}</div>
<div class="bl-loan-table__body">
@for (clients of officerTemplate.accountSummaryCollection.clients; track clients) {
<tr>
<table>
<tr>
<td>
<strong> {{ clients.displayName }} </strong>
</td>
</tr>
@for (loans of clients.loans; track loans) {
<tr>
<td>
<mat-checkbox (change)="getLoans($event, loans.id)"
>{{ loans.productName }}({{ loans.accountNo }})</mat-checkbox
>
</td>
</tr>
}
</table>
</tr>
<div class="bl-loan-group">
<div class="bl-loan-group__name">{{ clients.displayName }}</div>
@for (loans of clients.loans; track loans) {
<div class="bl-loan-group__item">
<mat-checkbox (change)="getLoans($event, loans.id)">
{{ loans.productName }}({{ loans.accountNo }})
</mat-checkbox>
</div>
}
</div>
}
</tbody>
</table>
}
</div>
</div>
@if (officerTemplate !== undefined) {
<table class="flex-48">
<thead>
<th>{{ 'labels.inputs.Groups' | translate }}</th>
</thead>
<tbody>
<div class="bl-loan-table">
<div class="bl-loan-table__header">{{ 'labels.inputs.Groups' | translate }}</div>
<div class="bl-loan-table__body">
@for (groups of officerTemplate.accountSummaryCollection.groups; track groups) {
<tr>
<table>
<tr>
<td>
<strong> {{ groups.displayName }} </strong>
</td>
</tr>
@for (loans of groups.loans; track loans) {
<tr>
<td>
<mat-checkbox (change)="getLoans($event, loans.id)"
>{{ loans.productName }}({{ loans.accountNo }})</mat-checkbox
>
</td>
</tr>
}
</table>
</tr>
<div class="bl-loan-group">
<div class="bl-loan-group__name">{{ groups.displayName }}</div>
@for (loans of groups.loans; track loans) {
<div class="bl-loan-group__item">
<mat-checkbox (change)="getLoans($event, loans.id)">
{{ loans.productName }}({{ loans.accountNo }})
</mat-checkbox>
</div>
}
</div>
}
</tbody>
</table>
}
</div>
</div>
</div>
</div>
</mat-card-content>
}
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<!-- Footer Actions -->
<div class="bl-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="bl-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
type="submit"
mat-raised-button
mat-flat-button
color="primary"
class="bl-actions__submit"
[disabled]="!bulkLoanForm.valid"
*mifosxHasPermission="'BULKREASSIGN_LOAN'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,32 +6,202 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
display: flex;
justify-content: center;
padding: 16px 12px;
}
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.container > mat-card {
width: 100%;
max-width: 560px;
.bl-container {
max-width: 860px;
margin: 0 auto;
border-radius: 4px;
padding: 16px 20px;
box-shadow:
0 1px 3px rgb(16 24 40 / 10%),
0 1px 2px rgb(16 24 40 / 6%);
padding: 28px 24px;
}
mat-form-field {
.bl-header {
margin-bottom: 24px;
&__title {
font-size: 22px;
font-weight: 600;
color: $gray-900;
margin: 16px 0 0;
}
}
.bl-back {
border: 1px solid $border !important;
color: $gray-700 !important;
font-size: 13px;
font-weight: 500;
border-radius: 8px !important;
padding: 4px 14px !important;
transition: all 0.2s ease;
i {
margin-right: 6px;
font-size: 12px;
}
&:hover {
background: $gray-50 !important;
border-color: $gray-500 !important;
}
}
.bl-card {
background: #fff;
border: 1px solid $border;
border-radius: 12px;
padding: 0;
box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04);
}
.bl-section {
padding: 24px 28px;
border-bottom: 1px solid $border;
&__header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
i {
font-size: 16px;
color: $primary;
}
h3 {
font-size: 15px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
}
.bl-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px 20px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.bl-field {
width: 100%;
}
.flex-48 {
flex: 1 1 100%;
min-width: 100%;
// Loan selection tables
.bl-tables {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
table {
width: 100%;
.bl-loan-table {
border: 1px solid $border;
border-radius: 8px;
overflow: hidden;
&__header {
background: $gray-50;
padding: 12px 16px;
font-size: 13px;
font-weight: 600;
color: $gray-700;
text-transform: uppercase;
letter-spacing: 0.4px;
border-bottom: 1px solid $border;
}
&__body {
padding: 8px 0;
max-height: 360px;
overflow-y: auto;
}
}
.bl-loan-group {
padding: 0 16px;
&__name {
font-size: 13px;
font-weight: 600;
color: $gray-900;
padding: 10px 0 4px;
border-bottom: 1px solid $gray-200;
margin-bottom: 4px;
}
&__item {
padding: 4px 0 4px 8px;
font-size: 13px;
color: $gray-700;
mat-checkbox {
font-size: 13px;
}
}
}
.bl-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
padding: 18px 28px;
&__cancel {
border: 1px solid $border !important;
color: $gray-700 !important;
border-radius: 8px !important;
font-weight: 500;
font-size: 13px;
padding: 4px 20px !important;
transition: all 0.2s ease;
&:hover {
background: $gray-50 !important;
border-color: $gray-500 !important;
}
}
&__submit {
background: $primary !important;
color: #fff !important;
border-radius: 8px !important;
font-weight: 500;
font-size: 13px;
padding: 4px 20px !important;
box-shadow: 0 1px 2px rgba(19, 83, 160, 0.15);
transition: all 0.2s ease;
i {
margin-right: 6px;
font-size: 12px;
}
&:hover {
background: #0f4688 !important;
box-shadow: 0 2px 6px rgba(19, 83, 160, 0.25);
}
&:disabled {
background: $gray-200 !important;
color: $gray-500 !important;
box-shadow: none;
cursor: not-allowed;
}
}
}
@@ -6,40 +6,50 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<div #buttonAddEdit class="in-block">
<button mat-raised-button color="primary" [routerLink]="'manage'" *mifosxHasPermission="'UPDATE_CURRENCY'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add/Edit' | translate }}
</button>
</div>
</div>
<div class="container">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-cogs title-icon"></i>
{{ 'labels.heading.Currency Configuration' | translate }}
</h2>
</div>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<div #buttonAddEdit class="in-block">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="'manage'" *mifosxHasPermission="'UPDATE_CURRENCY'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Add/Edit' | translate }}
</button>
</div>
</div>
</div>
</div>
<div #tableCurrencies class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Currency Name' | translate }}</th>
<td mat-cell *matCellDef="let currency">{{ currency.name }}</td>
</ng-container>
<div #filter class="table-container">
<div #tableCurrencies>
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Currency Name' | translate }}</th>
<td mat-cell *matCellDef="let currency">{{ currency.name }}</td>
</ng-container>
<ng-container matColumnDef="code">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Currency Code' | translate }}</th>
<td mat-cell *matCellDef="let currency">{{ currency.code }}</td>
</ng-container>
<ng-container matColumnDef="code">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Currency Code' | translate }}</th>
<td mat-cell *matCellDef="let currency">{{ currency.code }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</div>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
}
@@ -6,72 +6,98 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div #currencyFormRef class="currency-form-container">
<form #formRef="ngForm" [formGroup]="currencyForm" class="layout-row gap-20px" (ngSubmit)="addCurrency()">
<mat-form-field class="currency-search-field">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currency">
<mat-option>
<ngx-mat-select-search
placeholderLabel="{{ placeHolderLabel }}"
noEntriesFoundLabel="{{ noEntriesFoundLabel }}"
[formControl]="filterFormCtrl"
></ngx-mat-select-search>
</mat-option>
@for (currency of currencyData | async; track currency) {
<mat-option [value]="currency"> ({{ currency.code }}) {{ currency.name }} </mat-option>
}
</mat-select>
@if (currencyForm.controls.currency.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="mc-container">
<!-- Header -->
<div class="mc-header">
<button mat-stroked-button [routerLink]="['../']" class="mc-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Currency Configuration' | translate }}
</button>
<h2 class="mc-header__title">{{ 'labels.buttons.Add/Edit' | translate }}</h2>
</div>
<button
type="submit"
mat-raised-button
class="add-currency-button"
color="primary"
[disabled]="!currencyForm.valid"
[attr.aria-label]="'labels.buttons.Add Currency' | translate"
[title]="'labels.buttons.Add Currency' | translate"
*mifosxHasPermission="'UPDATE_CURRENCY'"
>
<fa-icon icon="plus"></fa-icon>
</button>
</form>
<!-- Card -->
<div class="mc-card">
<!-- Add Currency Section -->
<div class="mc-section">
<div class="mc-section__header">
<i class="fas fa-plus-circle"></i>
<h3>{{ 'labels.buttons.Add Currency' | translate }}</h3>
</div>
<div class="mc-add" #currencyFormRef>
<form #formRef="ngForm" [formGroup]="currencyForm" (ngSubmit)="addCurrency()" class="mc-add__form">
<mat-form-field appearance="outline" class="mc-add__field">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currency">
<mat-option>
<ngx-mat-select-search
placeholderLabel="{{ placeHolderLabel }}"
noEntriesFoundLabel="{{ noEntriesFoundLabel }}"
[formControl]="filterFormCtrl"
></ngx-mat-select-search>
</mat-option>
@for (currency of currencyData | async; track currency) {
<mat-option [value]="currency"> ({{ currency.code }}) {{ currency.name }} </mat-option>
}
</mat-select>
@if (currencyForm.controls.currency.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<button
type="submit"
mat-flat-button
color="primary"
class="mc-add__btn"
[disabled]="!currencyForm.valid"
[attr.aria-label]="'labels.buttons.Add Currency' | translate"
[title]="'labels.buttons.Add Currency' | translate"
*mifosxHasPermission="'UPDATE_CURRENCY'"
>
<i class="fas fa-plus"></i> Add
</button>
</form>
</div>
</div>
<mat-grid-list cols="2" rowHeight="50px">
@for (currency of selectedCurrencies; track currency; let i = $index) {
<mat-grid-tile class="grid-tile-content">
<div class="grid-container">
<div>
<button
type="button"
mat-button
color="warn"
(click)="deleteCurrency(currency.code, i)"
[attr.aria-label]="'labels.buttons.Delete' | translate"
[title]="'labels.buttons.Delete' | translate"
*mifosxHasPermission="'UPDATE_CURRENCY'"
>
<fa-icon icon="trash"></fa-icon>
</button>
<!-- Selected Currencies Section -->
<div class="mc-section">
<div class="mc-section__header">
<i class="fas fa-coins"></i>
<h3>Selected Currencies</h3>
</div>
<div class="mc-list">
@for (currency of selectedCurrencies; track currency; let i = $index) {
<div class="mc-currency">
<div class="mc-currency__info">
<span class="mc-currency__code">{{ currency.code }}</span>
<span class="mc-currency__name">{{ currency.name }}</span>
</div>
<span class="label m-l-10 p-t-5"
><b>{{ currency.name }}</b></span
<button
type="button"
mat-icon-button
class="mc-currency__delete"
(click)="deleteCurrency(currency.code, i)"
[attr.aria-label]="'labels.buttons.Delete' | translate"
[title]="'labels.buttons.Delete' | translate"
*mifosxHasPermission="'UPDATE_CURRENCY'"
>
<i class="fas fa-trash"></i>
</button>
</div>
</mat-grid-tile>
}
</mat-grid-list>
</mat-card>
}
@if (!selectedCurrencies || selectedCurrencies.length === 0) {
<div class="mc-empty">
<i class="fas fa-coins"></i>
<p>No currencies selected</p>
</div>
}
</div>
</div>
</div>
</div>
<ng-template #templateCurrencyFormRef let-popover="popover">
@@ -6,46 +6,121 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
width: 74rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.mc-container { max-width: 700px; margin: 0 auto; padding: 20px 16px; }
.mc-header {
display: flex; align-items: center; gap: 14px; margin-bottom: 20px;
&__title { font-size: 18px; font-weight: 600; color: $gray-900; margin: 0; }
}
.currency-form-container {
padding: 16px 24px;
margin-bottom: 16px;
.mc-back {
border-radius: 8px !important; font-size: 12px !important; font-weight: 600 !important;
height: 32px !important; border-color: $border !important; color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.currency-search-field {
flex: 1 1 auto;
min-width: 0;
margin-right: 20px;
.mc-card { background: #fff; border: 1px solid $border; border-radius: 10px; overflow: hidden; }
.mc-section {
&__header {
display: flex; align-items: center; gap: 10px; padding: 12px 24px;
background: $gray-50; border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
& + & { border-top: 1px solid $gray-200; }
}
.add-currency-button {
// Add currency form
.mc-add { padding: 16px 24px; }
.mc-add__form {
display: flex;
align-items: flex-start;
gap: 12px;
}
.mc-add__field { flex: 1; width: 100%; }
.mc-add__btn {
flex-shrink: 0;
min-width: 56px;
width: 56px;
padding: 0;
height: 36px !important;
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
margin-top: 4px;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
// Currency list
.mc-list { padding: 8px 24px 16px; }
.mc-currency {
display: flex;
align-items: center;
justify-content: center;
align-self: center;
margin-bottom: 35px;
justify-content: space-between;
padding: 10px 14px;
background: $gray-50;
border: 1px solid rgba(0, 0, 0, 0.04);
border-radius: 8px;
margin-bottom: 6px;
transition: background 0.12s;
&:hover { background: #f1f5f9; }
&__info {
display: flex;
align-items: center;
gap: 10px;
}
&__code {
font-size: 11px;
font-weight: 700;
color: $primary;
background: rgba($primary, 0.08);
padding: 2px 8px;
border-radius: 4px;
letter-spacing: 0.5px;
font-family: 'Roboto Mono', monospace;
}
&__name {
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
&__delete {
color: #ef4444 !important;
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
font-size: 13px;
&:hover { background: rgba(239, 68, 68, 0.08) !important; }
}
}
.currency-grid {
max-width: 40%;
justify-content: left !important;
}
mat-grid-tile {
justify-content: left !important;
}
.grid-container {
.mc-empty {
display: flex;
flex-direction: row;
justify-content: left;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
padding: 32px 16px;
color: #cbd5e1;
i { font-size: 28px; margin-bottom: 8px; }
p { font-size: 13px; color: $gray-500; margin: 0; }
}
@@ -6,29 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="employeeForm" (ngSubmit)="submit()">
<mat-card-content>
<div #createEmployeeFormRef class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="officeId">
@for (office of officeData; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
@if (employeeForm.controls.officeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ce-container">
<!-- Header -->
<div class="ce-header">
<button mat-stroked-button [routerLink]="['../']" class="ce-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Employees' | translate }}
</button>
<h2 class="ce-header__title">{{ 'labels.heading.Create Employee' | translate }}</h2>
</div>
<mat-form-field>
<!-- Form Card -->
<div class="ce-card">
<form [formGroup]="employeeForm" (ngSubmit)="submit()">
<!-- Personal Details Section -->
<div class="ce-section">
<div class="ce-section__header">
<i class="fas fa-user"></i>
<h3>Personal Details</h3>
</div>
<div class="ce-grid" #createEmployeeFormRef>
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.First Name' | translate }}</mat-label>
<input matInput required formControlName="firstname" />
@if (employeeForm.controls.firstname.hasError('required')) {
@@ -45,7 +42,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Last Name' | translate }}</mat-label>
<input matInput required formControlName="lastname" />
@if (employeeForm.controls.lastname.hasError('required')) {
@@ -61,26 +58,34 @@
</mat-error>
}
</mat-form-field>
</div>
</div>
<mat-checkbox labelPosition="before" formControlName="isLoanOfficer" class="loan-officer">
{{ 'labels.inputs.Is Loan Officer' | translate }}
</mat-checkbox>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Mobile Number for SMS' | translate }}</mat-label>
<input matInput formControlName="mobileNo" />
@if (
employeeForm.controls.mobileNo.hasError('pattern') &&
(employeeForm.controls.mobileNo.dirty || employeeForm.controls.mobileNo.touched)
) {
<!-- Assignment Section -->
<div class="ce-section">
<div class="ce-section__header">
<i class="fas fa-sitemap"></i>
<h3>Assignment</h3>
</div>
<div class="ce-grid">
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="officeId">
@for (office of officeData; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
@if (employeeForm.controls.officeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Mobile Number for SMS' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.invalid' | translate }}</strong>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field (click)="joiningDatePicker.open()">
<mat-form-field appearance="outline" class="ce-field" (click)="joiningDatePicker.open()">
<mat-label>{{ 'labels.inputs.Joining Date' | translate }}</mat-label>
<input
matInput
@@ -99,24 +104,47 @@
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="ce-checkbox-row">
<mat-checkbox labelPosition="after" formControlName="isLoanOfficer">
{{ 'labels.inputs.Is Loan Officer' | translate }}
</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="ce-field ce-field--full">
<mat-label>{{ 'labels.inputs.Mobile Number for SMS' | translate }}</mat-label>
<input matInput formControlName="mobileNo" />
@if (
employeeForm.controls.mobileNo.hasError('pattern') &&
(employeeForm.controls.mobileNo.dirty || employeeForm.controls.mobileNo.touched)
) {
<mat-error>
{{ 'labels.inputs.Mobile Number for SMS' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.invalid' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
<!-- Actions -->
<div class="ce-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ce-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!employeeForm.valid"
*mifosxHasPermission="'CREATE_STAFF'"
class="ce-actions__submit"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
<ng-template #templateCreateEmployeeForm let-popover="popover">
@@ -6,10 +6,140 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.ce-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.loan-officer {
padding: 17.5px 0;
// Header
.ce-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ce-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.ce-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.ce-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.ce-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ce-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.ce-checkbox-row {
grid-column: 1 / -1;
display: flex;
gap: 24px;
padding: 8px 0;
flex-wrap: wrap;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
}
}
// Actions
.ce-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,29 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<form [formGroup]="editEmployeeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="officeId">
@for (office of officeData; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
@if (editEmployeeForm.controls.officeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="ee-container">
<!-- Header -->
<div class="ee-header">
<button mat-stroked-button [routerLink]="['../']" class="ee-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Employees' | translate }}
</button>
<h2 class="ee-header__title">{{ 'labels.heading.Edit Employee' | translate }}</h2>
</div>
<mat-form-field>
<!-- Form Card -->
<div class="ee-card">
<form [formGroup]="editEmployeeForm" (ngSubmit)="submit()">
<!-- Personal Details Section -->
<div class="ee-section">
<div class="ee-section__header">
<i class="fas fa-user"></i>
<h3>Personal Details</h3>
</div>
<div class="ee-grid">
<mat-form-field appearance="outline" class="ee-field">
<mat-label>{{ 'labels.inputs.First Name' | translate }}</mat-label>
<input matInput required formControlName="firstname" />
@if (editEmployeeForm.controls.firstname.hasError('required')) {
@@ -45,7 +42,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="ee-field">
<mat-label>{{ 'labels.inputs.Last Name' | translate }}</mat-label>
<input matInput required formControlName="lastname" />
@if (editEmployeeForm.controls.lastname.hasError('required')) {
@@ -61,21 +58,34 @@
</mat-error>
}
</mat-form-field>
</div>
</div>
<mat-checkbox labelPosition="before" formControlName="isLoanOfficer" class="m-b-10">
{{ 'labels.inputs.Is Loan Officer' | translate }}
</mat-checkbox>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Mobile Number for SMS' | translate }}</mat-label>
<input matInput formControlName="mobileNo" />
<!-- Assignment Section -->
<div class="ee-section">
<div class="ee-section__header">
<i class="fas fa-sitemap"></i>
<h3>Assignment</h3>
</div>
<div class="ee-grid">
<mat-form-field appearance="outline" class="ee-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select required formControlName="officeId">
@for (office of officeData; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
@if (editEmployeeForm.controls.officeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Office' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="isActive">
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
<mat-form-field (click)="joiningDatePicker.open()">
<mat-form-field appearance="outline" class="ee-field" (click)="joiningDatePicker.open()">
<mat-label>{{ 'labels.inputs.Joining Date' | translate }}</mat-label>
<input
matInput
@@ -94,22 +104,40 @@
</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="ee-checkbox-row">
<mat-checkbox labelPosition="after" formControlName="isLoanOfficer">
{{ 'labels.inputs.Is Loan Officer' | translate }}
</mat-checkbox>
<mat-checkbox labelPosition="after" formControlName="isActive">
{{ 'labels.inputs.Active' | translate }}
</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="ee-field ee-field--full">
<mat-label>{{ 'labels.inputs.Mobile Number for SMS' | translate }}</mat-label>
<input matInput formControlName="mobileNo" />
</mat-form-field>
</div>
</div>
<!-- Actions -->
<div class="ee-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ee-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!editEmployeeForm.valid"
*mifosxHasPermission="'UPDATE_STAFF'"
class="ee-actions__submit"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,140 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.ee-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.ee-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ee-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.ee-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.ee-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.ee-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ee-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
.ee-checkbox-row {
grid-column: 1 / -1;
display: flex;
gap: 24px;
padding: 8px 0;
flex-wrap: wrap;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
}
}
// Actions
.ee-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,36 +6,41 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<div #buttonImportEmployees>
<button
mat-raised-button
color="primary"
*mifosxHasPermission="'CREATE_STAFF'"
[routerLink]="['/', 'organization', 'bulk-import', 'Employees']"
>
<fa-icon icon="upload" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Import Employees' | translate }}
</button>
</div>
<div #buttonCreateEmployee>
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_STAFF'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Employee' | translate }}
</button>
</div>
</div>
<div class="container">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-user title-icon"></i>
{{ 'labels.heading.Manage Employees' | translate }}
</h2>
</div>
<div class="table-actions">
<div #filter class="table-search">
<mat-form-field appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
<div class="table-buttons">
<div #buttonImportEmployees>
<button mat-stroked-button class="btn-import" *mifosxHasPermission="'CREATE_STAFF'" [routerLink]="['/', 'organization', 'bulk-import', 'Employees']">
<fa-icon icon="upload" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Import Employees' | translate }}
</button>
</div>
<div #buttonCreateEmployee>
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_STAFF'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Employee' | translate }}
</button>
</div>
</div>
</div>
</div>
<div #tableEmployees class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div #tableEmployees class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="firstname">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.First Name' | translate }}</th>
<td mat-cell *matCellDef="let employee">{{ employee.firstname }}</td>
@@ -90,9 +95,8 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,77 +6,76 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_STAFF'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="ve-container">
<!-- Header -->
<div class="ve-header">
<div class="ve-header__left">
<button mat-stroked-button [routerLink]="['../']" class="ve-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Employees' | translate }}
</button>
<h2 class="ve-header__title">{{ employeeData.firstname }} {{ employeeData.lastname }}</h2>
</div>
<button mat-flat-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_STAFF'" class="ve-header__edit">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.First Name' | translate }}
</div>
<!-- Detail Card -->
<div class="ve-card">
<div class="ve-grid">
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.First Name' | translate }}</div>
<div class="ve-cell__value">{{ employeeData.firstname }}</div>
</div>
<div class="flex-50">
{{ employeeData.firstname }}
</div>
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.Last Name' | translate }}</div>
<div class="ve-cell__value">{{ employeeData.lastname }}</div>
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Last Name' | translate }}
</div>
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.Office' | translate }}</div>
<div class="ve-cell__value">{{ employeeData.officeName }}</div>
</div>
<div class="flex-50">
{{ employeeData.lastname }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Office' | translate }}
</div>
<div class="flex-50">
{{ employeeData.officeName }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Is Loan Officer' | translate }}
</div>
<div class="flex-50">
{{ employeeData.isLoanOfficer === true | yesNo }}
</div>
@if (employeeData.mobileNo) {
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Mobile Number for SMS' | translate }}
</div>
}
@if (employeeData.mobileNo) {
<div class="flex-50">
{{ employeeData.mobileNo }}
</div>
}
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Status' | translate }}
</div>
<div class="flex-50">
{{ employeeData.isActive === true ? 'Active' : 'Inactive' }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Joining Date' | translate }}
</div>
<div class="flex-50">
{{ employeeData.joiningDate | dateFormat }}
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.Is Loan Officer' | translate }}</div>
<div class="ve-cell__value">
@if (employeeData.isLoanOfficer) {
<span class="ve-bool ve-bool--yes">
<i class="fas fa-check"></i> Yes
</span>
} @else {
<span class="ve-bool ve-bool--no">
<i class="fas fa-times"></i> No
</span>
}
</div>
</div>
</mat-card-content>
</mat-card>
@if (employeeData.mobileNo) {
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.Mobile Number for SMS' | translate }}</div>
<div class="ve-cell__value">{{ employeeData.mobileNo }}</div>
</div>
}
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.Status' | translate }}</div>
<div class="ve-cell__value">
@if (employeeData.isActive) {
<span class="ve-badge ve-badge--active">Active</span>
} @else {
<span class="ve-badge ve-badge--inactive">Inactive</span>
}
</div>
</div>
<div class="ve-cell">
<div class="ve-cell__label">{{ 'labels.inputs.Joining Date' | translate }}</div>
<div class="ve-cell__value">{{ employeeData.joiningDate | dateFormat }}</div>
</div>
</div>
</div>
</div>
@@ -6,13 +6,149 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
}
.ve-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.ve-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 20px;
flex-wrap: wrap;
&__left {
display: flex;
align-items: center;
gap: 14px;
}
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
&__edit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
}
}
.ve-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.ve-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Grid
.ve-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
padding: 20px 28px 24px;
background: #fff;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ve-cell {
padding: 14px 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.04);
margin: 3px;
transition: background 0.12s;
&:hover {
background: #f1f5f9;
}
&__label {
font-size: 10px;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
&__value {
font-size: 14px;
font-weight: 600;
color: #1e293b;
line-height: 1.4;
}
}
// Status badge
.ve-badge {
display: inline-flex;
align-items: center;
padding: 3px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.2px;
&--active {
background: rgba(22, 163, 74, 0.1);
color: #16a34a;
}
&--inactive {
background: rgba(220, 38, 38, 0.1);
color: #dc2626;
}
}
// Boolean indicator (Yes/No with icon)
.ve-bool {
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 600;
font-size: 13px;
i { font-size: 11px; }
&--yes {
color: #16a34a;
}
&--no {
color: #dc2626;
}
}
@@ -11,7 +11,6 @@ import { Component, inject } from '@angular/core';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { YesnoPipe } from '../../../pipes/yesno.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -24,8 +23,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
DateFormatPipe,
YesnoPipe
DateFormatPipe
]
})
export class ViewEmployeeComponent {
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="ce-container">
<!-- Header -->
<div class="ce-header">
<button mat-stroked-button [routerLink]="['../']" class="ce-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Entity Data Table Checks' | translate }}
</button>
<h2 class="ce-header__title">{{ 'labels.heading.Create Entity Data Table Check' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="ce-card">
<form [formGroup]="createEntityForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<!-- Check Configuration Section -->
<div class="ce-section">
<div class="ce-section__header">
<i class="fas fa-check-circle"></i>
<h3>Check Configuration</h3>
</div>
<div class="ce-grid">
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Entity' | translate }}</mat-label>
<mat-select required formControlName="entity">
@for (entity of entityTypes; track entity) {
@@ -28,7 +42,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Status' | translate }}</mat-label>
<mat-select required formControlName="status">
@for (status of statusList; track status) {
@@ -45,7 +59,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Data Table' | translate }}</mat-label>
<mat-select required formControlName="datatableName">
@for (dataTable of dataTableList; track dataTable) {
@@ -63,7 +77,7 @@
</mat-form-field>
@if (entityType === 'm_loan') {
<mat-form-field>
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Products' | translate }}</mat-label>
<mat-select required formControlName="productId">
@for (loanProducts of createEntityData.loanProductDatas; track loanProducts) {
@@ -82,7 +96,7 @@
}
@if (entityType === 'm_savings_account') {
<mat-form-field>
<mat-form-field appearance="outline" class="ce-field">
<mat-label>{{ 'labels.inputs.Products' | translate }}</mat-label>
<mat-select required formControlName="productId">
@for (savingsProducts of createEntityData.savingsProductDatas; track savingsProducts) {
@@ -100,16 +114,17 @@
</mat-form-field>
}
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<!-- Actions -->
<div class="ce-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ce-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!createEntityForm.valid">
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="!createEntityForm.valid" class="ce-actions__submit">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,120 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.ce-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.ce-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ce-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.ce-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.ce-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
// Grid
.ce-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ce-field {
width: 100%;
}
// Actions
.ce-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,28 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button
mat-raised-button
color="primary"
[routerLink]="['create']"
*mifosxHasPermission="'CREATE_ENTITY_DATATABLE_CHECK'"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create' | translate }}
</button>
</div>
<mat-card class="container">
<!-- Header Bar -->
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-check"></i>
{{ 'labels.heading.Entity Data Table Checks' | translate }}
</h2>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<input matInput placeholder="{{ 'labels.inputs.Filter' | translate }}" (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" [routerLink]="['create']" class="btn-create" *mifosxHasPermission="'CREATE_ENTITY_DATATABLE_CHECK'">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Create' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Table -->
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="entity">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Entity' | translate }}</th>
<td mat-cell *matCellDef="let entityDataTableCheck">{{ entityDataTableCheck.entity }}</td>
@@ -92,4 +98,4 @@
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</mat-card>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
}
@@ -7,314 +7,372 @@
-->
@if (!isCollapsed) {
<mat-card class="container">
<form [formGroup]="fundMappingForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2percent responsive-column align-start-center">
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Loan Status' | translate }}</mat-label>
<mat-select formControlName="loanStatus" multiple>
<mat-option value="all">{{ 'labels.inputs.All' | translate }}</mat-option>
<mat-option value="active">{{ 'labels.inputs.Active' | translate }}</mat-option>
<mat-option value="closed">{{ 'labels.inputs.Overpaid' | translate }}</mat-option>
<mat-option value="overpaid">{{ 'labels.inputs.Closed (obligations met)' | translate }}</mat-option>
<mat-option value="writeoff">{{ 'labels.inputs.Closed (written-off)' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<span class="flex-48 hide-lt-md"></span>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Product' | translate }}</mat-label>
<mat-select formControlName="loanProducts" multiple>
@for (product of advanceSearchTemplate.loanProducts; track product) {
<mat-option [value]="product.id">
{{ product.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-48">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select formControlName="offices" multiple>
@for (office of advanceSearchTemplate.offices; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Date Type' | translate }}</mat-label>
<mat-select required formControlName="loanDateOption">
<mat-option value="approvalDate">{{ 'labels.inputs.Approval Date' | translate }}</mat-option>
<mat-option value="createdDate">{{ 'labels.inputs.Creation Date' | translate }}</mat-option>
<mat-option value="disbursalDate">{{ 'labels.inputs.Disbursement Date' | translate }}</mat-option>
</mat-select>
@if (fundMappingForm.controls.loanDateOption.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Date Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-31" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="fromDatePicker"
formControlName="loanFromDate"
required
placeholder="{{ 'labels.inputs.From Date' | translate }}"
/>
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #fromDatePicker></mat-datepicker>
@if (fundMappingForm.controls.loanFromDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.From Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-31" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="toDatePicker"
formControlName="loanToDate"
required
placeholder="{{ 'labels.inputs.To Date' | translate }}"
/>
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
<mat-datepicker #toDatePicker></mat-datepicker>
@if (fundMappingForm.controls.loanToDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.To Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-checkbox
labelPosition="before"
formControlName="includeOutStandingAmountPercentage"
class="margin-b flex-73"
>
{{ 'labels.inputs.Loan Outstanding Percentage' | translate }}
</mat-checkbox>
@if (fundMappingForm.value.includeOutStandingAmountPercentage) {
<div class="flex-fill layout-row-wrap gap-2percent layout-xs-column">
@if (fundMappingForm.contains('outStandingAmountPercentageCondition')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Comparison Condition' | translate }}</mat-label>
<mat-select required formControlName="outStandingAmountPercentageCondition">
<mat-option value="between">{{ 'labels.inputs.between' | translate }}</mat-option>
<mat-option value="<=">&lt;=</mat-option>
<mat-option value=">=">&gt;=</mat-option>
<mat-option value="<">&lt;</mat-option>
<mat-option value=">">&gt;</mat-option>
<mat-option value="=">=</mat-option>
</mat-select>
@if (fundMappingForm.controls.outStandingAmountPercentageCondition.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Condition' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<div class="fm-container">
<!-- Header -->
<div class="fm-header">
<button mat-stroked-button [routerLink]="['../']" class="fm-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.buttons.Cancel' | translate }}
</button>
<h2 class="fm-header__title">Fund Mapping</h2>
</div>
@if (fundMappingForm.contains('minOutStandingAmountPercentage')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Minimum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="minOutStandingAmountPercentage"
placeholder="{{ 'labels.inputs.Minimum Value' | translate }}"
/>
@if (fundMappingForm.controls.minOutStandingAmountPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Minimum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('outStandingAmountPercentage')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Comparison Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="outStandingAmountPercentage"
placeholder="{{ 'labels.inputs.Comparison Value' | translate }}"
/>
@if (fundMappingForm.controls.outStandingAmountPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('maxOutStandingAmountPercentage')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Maximum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="maxOutStandingAmountPercentage"
placeholder="{{ 'labels.inputs.Maximum Value' | translate }}"
/>
@if (fundMappingForm.controls.maxOutStandingAmountPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Maximum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
</div>
}
<mat-checkbox class="flex-73 margin-b" labelPosition="before" formControlName="includeOutstandingAmount">
{{ 'labels.inputs.Loan Outstanding Amount' | translate }}
</mat-checkbox>
@if (fundMappingForm.value.includeOutstandingAmount) {
<div class="flex-fill layout-row-wrap gap-2percent responsive-column">
@if (fundMappingForm.contains('outstandingAmountCondition')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Comparison Condition' | translate }}</mat-label>
<mat-select required formControlName="outstandingAmountCondition">
<mat-option value="between">{{ 'labels.inputs.between' | translate }}</mat-option>
<mat-option value="<=">&lt;=</mat-option>
<mat-option value=">=">&gt;=</mat-option>
<mat-option value="<">&lt;</mat-option>
<mat-option value=">">&gt;</mat-option>
<mat-option value="=">=</mat-option>
</mat-select>
@if (fundMappingForm.controls.outstandingAmountCondition.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Condition' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<!-- Form Card -->
<div class="fm-card">
<form [formGroup]="fundMappingForm" (ngSubmit)="submit()">
<!-- Section 1: Filter Criteria -->
<div class="fm-section">
<div class="fm-section__header">
<i class="fas fa-filter"></i>
<h3>Filter Criteria</h3>
</div>
<div class="fm-grid fm-grid--3col">
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Loan Status' | translate }}</mat-label>
<mat-select formControlName="loanStatus" multiple>
<mat-option value="all">{{ 'labels.inputs.All' | translate }}</mat-option>
<mat-option value="active">{{ 'labels.inputs.Active' | translate }}</mat-option>
<mat-option value="closed">{{ 'labels.inputs.Overpaid' | translate }}</mat-option>
<mat-option value="overpaid">{{ 'labels.inputs.Closed (obligations met)' | translate }}</mat-option>
<mat-option value="writeoff">{{ 'labels.inputs.Closed (written-off)' | translate }}</mat-option>
</mat-select>
</mat-form-field>
@if (fundMappingForm.contains('minOutstandingAmount')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Minimum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="minOutstandingAmount"
placeholder="{{ 'labels.inputs.Minimum Value' | translate }}"
/>
@if (fundMappingForm.controls.minOutstandingAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Minimum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('outstandingAmount')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Comparison Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="outstandingAmount"
placeholder="{{ 'labels.inputs.Comparison Value' | translate }}"
/>
@if (fundMappingForm.controls.outstandingAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('maxOutstandingAmount')) {
<mat-form-field class="flex-31">
<mat-label>{{ 'labels.inputs.Maximum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="maxOutstandingAmount"
placeholder="{{ 'labels.inputs.Maximum Value' | translate }}"
/>
@if (fundMappingForm.controls.maxOutstandingAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Maximum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
</div>
}
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Product' | translate }}</mat-label>
<mat-select formControlName="loanProducts" multiple>
@for (product of advanceSearchTemplate.loanProducts; track product) {
<mat-option [value]="product.id">
{{ product.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<mat-select formControlName="offices" multiple>
@for (office of advanceSearchTemplate.offices; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" type="submit" [disabled]="!fundMappingForm.valid">
<fa-icon icon="search"></fa-icon>&nbsp; {{ 'labels.buttons.Summary' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
<!-- Section 2: Date Range -->
<div class="fm-section">
<div class="fm-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>Date Range</h3>
</div>
<div class="fm-grid fm-grid--3col">
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Date Type' | translate }}</mat-label>
<mat-select required formControlName="loanDateOption">
<mat-option value="approvalDate">{{ 'labels.inputs.Approval Date' | translate }}</mat-option>
<mat-option value="createdDate">{{ 'labels.inputs.Creation Date' | translate }}</mat-option>
<mat-option value="disbursalDate">{{ 'labels.inputs.Disbursement Date' | translate }}</mat-option>
</mat-select>
@if (fundMappingForm.controls.loanDateOption.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Date Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="fm-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="fromDatePicker"
formControlName="loanFromDate"
required
placeholder="{{ 'labels.inputs.From Date' | translate }}"
/>
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #fromDatePicker></mat-datepicker>
@if (fundMappingForm.controls.loanFromDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.From Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="fm-field" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="toDatePicker"
formControlName="loanToDate"
required
placeholder="{{ 'labels.inputs.To Date' | translate }}"
/>
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
<mat-datepicker #toDatePicker></mat-datepicker>
@if (fundMappingForm.controls.loanToDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.To Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
</div>
</div>
<!-- Section 3: Outstanding Criteria -->
<div class="fm-section">
<div class="fm-section__header">
<i class="fas fa-percentage"></i>
<h3>Outstanding Criteria</h3>
</div>
<div class="fm-grid">
<!-- Loan Outstanding Percentage -->
<div class="fm-checkbox-row">
<mat-checkbox
labelPosition="before"
formControlName="includeOutStandingAmountPercentage"
>
{{ 'labels.inputs.Loan Outstanding Percentage' | translate }}
</mat-checkbox>
</div>
@if (fundMappingForm.value.includeOutStandingAmountPercentage) {
<div class="fm-conditional-fields">
@if (fundMappingForm.contains('outStandingAmountPercentageCondition')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Comparison Condition' | translate }}</mat-label>
<mat-select required formControlName="outStandingAmountPercentageCondition">
<mat-option value="between">{{ 'labels.inputs.between' | translate }}</mat-option>
<mat-option value="<=">&lt;=</mat-option>
<mat-option value=">=">&gt;=</mat-option>
<mat-option value="<">&lt;</mat-option>
<mat-option value=">">&gt;</mat-option>
<mat-option value="=">=</mat-option>
</mat-select>
@if (fundMappingForm.controls.outStandingAmountPercentageCondition.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Condition' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('minOutStandingAmountPercentage')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Minimum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="minOutStandingAmountPercentage"
placeholder="{{ 'labels.inputs.Minimum Value' | translate }}"
/>
@if (fundMappingForm.controls.minOutStandingAmountPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Minimum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('outStandingAmountPercentage')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Comparison Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="outStandingAmountPercentage"
placeholder="{{ 'labels.inputs.Comparison Value' | translate }}"
/>
@if (fundMappingForm.controls.outStandingAmountPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('maxOutStandingAmountPercentage')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Maximum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="maxOutStandingAmountPercentage"
placeholder="{{ 'labels.inputs.Maximum Value' | translate }}"
/>
@if (fundMappingForm.controls.maxOutStandingAmountPercentage.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Maximum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
</div>
}
<!-- Loan Outstanding Amount -->
<div class="fm-checkbox-row">
<mat-checkbox
labelPosition="before"
formControlName="includeOutstandingAmount"
>
{{ 'labels.inputs.Loan Outstanding Amount' | translate }}
</mat-checkbox>
</div>
@if (fundMappingForm.value.includeOutstandingAmount) {
<div class="fm-conditional-fields">
@if (fundMappingForm.contains('outstandingAmountCondition')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Comparison Condition' | translate }}</mat-label>
<mat-select required formControlName="outstandingAmountCondition">
<mat-option value="between">{{ 'labels.inputs.between' | translate }}</mat-option>
<mat-option value="<=">&lt;=</mat-option>
<mat-option value=">=">&gt;=</mat-option>
<mat-option value="<">&lt;</mat-option>
<mat-option value=">">&gt;</mat-option>
<mat-option value="=">=</mat-option>
</mat-select>
@if (fundMappingForm.controls.outstandingAmountCondition.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Condition' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('minOutstandingAmount')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Minimum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="minOutstandingAmount"
placeholder="{{ 'labels.inputs.Minimum Value' | translate }}"
/>
@if (fundMappingForm.controls.minOutstandingAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Minimum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('outstandingAmount')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Comparison Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="outstandingAmount"
placeholder="{{ 'labels.inputs.Comparison Value' | translate }}"
/>
@if (fundMappingForm.controls.outstandingAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Comparison Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
@if (fundMappingForm.contains('maxOutstandingAmount')) {
<mat-form-field appearance="outline" class="fm-field">
<mat-label>{{ 'labels.inputs.Maximum Value' | translate }}</mat-label>
<input
type="number"
matInput
required
min="0"
formControlName="maxOutstandingAmount"
placeholder="{{ 'labels.inputs.Maximum Value' | translate }}"
/>
@if (fundMappingForm.controls.maxOutstandingAmount.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Maximum Value' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
</div>
}
</div>
</div>
<!-- Actions -->
<div class="fm-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="fm-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button type="submit" mat-flat-button color="primary" [disabled]="!fundMappingForm.valid" class="fm-actions__submit">
<i class="fas fa-search"></i> {{ 'labels.buttons.Summary' | translate }}
</button>
</div>
</form>
</div>
</div>
}
@if (isCollapsed) {
<div class="container">
<mat-card>
<div class="m-b-20">
<button mat-raised-button color="primary" (click)="isCollapsed = false">
{{ 'labels.buttons.Parameters' | translate }}
</button>
<div class="fm-container">
<!-- Header -->
<div class="fm-header">
<button mat-stroked-button (click)="isCollapsed = false" class="fm-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.buttons.Parameters' | translate }}
</button>
<h2 class="fm-header__title">Fund Mapping Results</h2>
</div>
<!-- Results Card -->
<div class="fm-card">
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="officeName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Office Name' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.officeName }}</td>
</ng-container>
<ng-container matColumnDef="productName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Product Name' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.loanProductName }}</td>
</ng-container>
<ng-container matColumnDef="count">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Count' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.count }}</td>
</ng-container>
<ng-container matColumnDef="outstanding">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Outstanding' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.loanOutStanding }}</td>
</ng-container>
<ng-container matColumnDef="percentage">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Percentage' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.percentage }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
<table class="mat-elevation-z1" mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="officeName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Office Name' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.officeName }}</td>
</ng-container>
<ng-container matColumnDef="productName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Product Name' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.loanProductName }}</td>
</ng-container>
<ng-container matColumnDef="count">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Count' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.count }}</td>
</ng-container>
<ng-container matColumnDef="outstanding">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Outstanding' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.loanOutStanding }}</td>
</ng-container>
<ng-container matColumnDef="percentage">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Percentage' | translate }}</th>
<td mat-cell *matCellDef="let loan">{{ loan.percentage }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</mat-card>
</div>
</div>
}
@@ -6,18 +6,188 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 73rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.margin-v {
margin: 1em 0;
}
.fm-container {
max-width: 860px;
margin: 0 auto;
padding: 20px 16px;
}
.margin-b {
margin: 0 0 1em;
}
// Header
.fm-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
.margin-t {
margin-top: 1em;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.fm-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.fm-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.fm-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.fm-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
&--3col {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
grid-template-columns: 1fr;
&--3col { grid-template-columns: 1fr; }
}
}
.fm-field {
width: 100%;
}
// Checkbox row
.fm-checkbox-row {
grid-column: 1 / -1;
padding: 8px 0;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
font-weight: 500;
}
}
// Conditional fields
.fm-conditional-fields {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px 16px;
padding: 0 0 8px;
margin-top: -4px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}
// Actions
.fm-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
// Results table
.table-container {
overflow-x: auto;
}
.modern-table {
width: 100%;
th.mat-header-cell {
font-size: 12px;
font-weight: 600;
color: $gray-700;
background: $gray-50;
padding: 12px 16px;
border-bottom: 1px solid $gray-200;
text-transform: uppercase;
letter-spacing: 0.4px;
}
td.mat-cell {
font-size: 13px;
color: $gray-900;
padding: 11px 16px;
border-bottom: 1px solid $border;
}
tr.mat-row:hover {
background: rgba($accent, 0.04);
}
}
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="ch-container">
<!-- Header -->
<div class="ch-header">
<button mat-stroked-button [routerLink]="['../']" class="ch-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Holidays' | translate }}
</button>
<h2 class="ch-header__title">{{ 'labels.heading.Create Holiday' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="ch-card">
<form [formGroup]="holidayForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<!-- Holiday Details Section -->
<div class="ch-section">
<div class="ch-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.heading.Holiday Details' | translate }}</h3>
</div>
<div class="ch-grid">
<mat-form-field appearance="outline" class="ch-field">
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required autofocus formControlName="name" />
@if (holidayForm.controls.name.hasError('required')) {
@@ -22,7 +36,12 @@
}
</mat-form-field>
<mat-form-field (click)="fromDatePicker.open()">
<mat-form-field appearance="outline" class="ch-field ch-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<input matInput formControlName="description" />
</mat-form-field>
<mat-form-field appearance="outline" class="ch-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
@@ -42,7 +61,7 @@
}
</mat-form-field>
<mat-form-field (click)="toDatePicker.open()">
<mat-form-field appearance="outline" class="ch-field" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
<input
matInput
@@ -61,8 +80,17 @@
</mat-error>
}
</mat-form-field>
</div>
</div>
<mat-form-field>
<!-- Scheduling Section -->
<div class="ch-section">
<div class="ch-section__header">
<i class="fas fa-clock"></i>
<h3>{{ 'labels.heading.Scheduling' | translate }}</h3>
</div>
<div class="ch-grid">
<mat-form-field appearance="outline" class="ch-field">
<mat-label>{{ 'labels.inputs.Repayment Scheduling Type' | translate }}</mat-label>
<mat-select required formControlName="reschedulingType">
@for (repayments of repaymentSchedulingTypes; track repayments) {
@@ -80,7 +108,7 @@
</mat-form-field>
@if (holidayForm.contains('repaymentsRescheduledTo')) {
<mat-form-field (click)="repaymentDatePicker.open()">
<mat-form-field appearance="outline" class="ch-field" (click)="repaymentDatePicker.open()">
<mat-label>{{ 'labels.inputs.Repayment scheduled to' | translate }}</mat-label>
<input
matInput
@@ -100,14 +128,16 @@
}
</mat-form-field>
}
</div>
</div>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<input matInput formControlName="description" />
</mat-form-field>
<label>{{ 'labels.inputs.Select applicable offices' | translate }}</label>
<!-- Applicable Offices Section -->
<div class="ch-section">
<div class="ch-section__header">
<i class="fas fa-building"></i>
<h3>{{ 'labels.inputs.Select applicable offices' | translate }}</h3>
</div>
<div class="ch-tree-wrapper">
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding>
<button mat-icon-button disabled></button>
@@ -131,21 +161,24 @@
</mat-tree-node>
</mat-tree>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<!-- Actions -->
<div class="ch-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ch-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!holidayForm.valid"
class="ch-actions__submit"
*mifosxHasPermission="'CREATE_HOLIDAY'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,143 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.ch-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.ch-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ch-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.ch-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.ch-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.ch-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ch-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
// Tree wrapper
.ch-tree-wrapper {
padding: 16px 24px;
::ng-deep {
.mat-tree {
background: transparent;
}
.mat-tree-node {
min-height: 36px;
}
}
}
// Actions
.ch-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="eh-container">
<!-- Header -->
<div class="eh-header">
<button mat-stroked-button [routerLink]="['../']" class="eh-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Holiday' | translate }}
</button>
<h2 class="eh-header__title">{{ 'labels.heading.Edit Holiday' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="eh-card">
<form [formGroup]="holidayForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<!-- Holiday Details Section -->
<div class="eh-section">
<div class="eh-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.heading.Holiday Details' | translate }}</h3>
</div>
<div class="eh-grid">
<mat-form-field appearance="outline" class="eh-field">
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required autofocus formControlName="name" />
@if (holidayForm.controls.name.hasError('required')) {
@@ -22,8 +36,13 @@
}
</mat-form-field>
<mat-form-field appearance="outline" class="eh-field eh-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<input matInput formControlName="description" />
</mat-form-field>
@if (!isActiveHoliday) {
<mat-form-field (click)="fromDatePicker.open()">
<mat-form-field appearance="outline" class="eh-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
@@ -45,7 +64,7 @@
}
@if (!isActiveHoliday) {
<mat-form-field (click)="toDatePicker.open()">
<mat-form-field appearance="outline" class="eh-field" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
<input
matInput
@@ -65,9 +84,18 @@
}
</mat-form-field>
}
</div>
</div>
@if (!isActiveHoliday) {
<mat-form-field>
<!-- Scheduling Section -->
@if (!isActiveHoliday) {
<div class="eh-section">
<div class="eh-section__header">
<i class="fas fa-clock"></i>
<h3>{{ 'labels.heading.Scheduling' | translate }}</h3>
</div>
<div class="eh-grid">
<mat-form-field appearance="outline" class="eh-field">
<mat-label>{{ 'labels.inputs.Repayment Scheduling Type' | translate }}</mat-label>
<mat-select required formControlName="reschedulingType">
@for (repayments of holidayData.repaymentSchedulingTypes; track repayments) {
@@ -83,50 +111,48 @@
</mat-error>
}
</mat-form-field>
}
@if (!isActiveHoliday && reSchedulingType === 2) {
<mat-form-field (click)="repaymentDatePicker.open()">
<mat-label>{{ 'labels.inputs.Repayment scheduled to' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="repaymentDatePicker"
required
formControlName="repaymentsRescheduledTo"
/>
<mat-datepicker-toggle matSuffix [for]="repaymentDatePicker"></mat-datepicker-toggle>
<mat-datepicker #repaymentDatePicker></mat-datepicker>
@if (holidayForm.controls.repaymentsRescheduledTo.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Repayment scheduled to' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
<mat-form-field>
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<input matInput formControlName="description" />
</mat-form-field>
@if (reSchedulingType === 2) {
<mat-form-field appearance="outline" class="eh-field" (click)="repaymentDatePicker.open()">
<mat-label>{{ 'labels.inputs.Repayment scheduled to' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="repaymentDatePicker"
required
formControlName="repaymentsRescheduledTo"
/>
<mat-datepicker-toggle matSuffix [for]="repaymentDatePicker"></mat-datepicker-toggle>
<mat-datepicker #repaymentDatePicker></mat-datepicker>
@if (holidayForm.controls.repaymentsRescheduledTo.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Repayment scheduled to' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
}
</div>
</div>
</mat-card-content>
}
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<!-- Actions -->
<div class="eh-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="eh-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!holidayForm.valid"
class="eh-actions__submit"
*mifosxHasPermission="'UPDATE_HOLIDAY'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,128 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.eh-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.eh-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.eh-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.eh-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.eh-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.eh-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.eh-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
// Actions
.eh-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,35 +6,43 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<div #buttonCreateHoliday class="in-block">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_HOLIDAY'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Holiday' | translate }}
</button>
</div>
</div>
<div class="container">
<div #filterRef class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<mat-form-field>
<mat-label> {{ 'labels.inputs.Select Office' | translate }} </mat-label>
<mat-select [formControl]="officeSelector">
@for (office of officeData; track office) {
<mat-option [value]="office.id">
{{ office.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-calendar-alt title-icon"></i>
{{ 'labels.heading.Manage Holidays' | translate }}
</h2>
</div>
<div class="table-actions">
<div #filterRef class="table-search">
<mat-form-field appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
<mat-form-field appearance="outline" style="min-width: 180px;">
<mat-label>{{ 'labels.inputs.Select Office' | translate }}</mat-label>
<mat-select [formControl]="officeSelector">
@for (office of officeData; track office) {
<mat-option [value]="office.id">{{ office.name }}</mat-option>
}
</mat-select>
</mat-form-field>
<div class="table-buttons">
<div #buttonCreateHoliday>
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_HOLIDAY'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Holiday' | translate }}
</button>
</div>
</div>
</div>
</div>
<div class="mat-elevation-z8" [hidden]="!officeSelector.value">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container" [hidden]="!officeSelector.value">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Holiday Name' | translate }}</th>
<td mat-cell *matCellDef="let holidays">{{ holidays.name }}</td>
@@ -67,9 +75,8 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="select-row" [routerLink]="[row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row" [routerLink]="[row.id]"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,69 +6,69 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row align-end gap-2px responsive-column container m-b-20">
<div class="vh-container">
<!-- Header -->
<div class="vh-header">
<div class="vh-header__left">
<button mat-stroked-button [routerLink]="['../']" class="vh-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Holidays' | translate }}
</button>
<h2 class="vh-header__title">{{ holidayData.name }}</h2>
</div>
<button mat-icon-button [matMenuTriggerFor]="actionMenu" class="vh-menu-btn" aria-label="Holiday actions">
<fa-icon icon="ellipsis-v"></fa-icon>
</button>
</div>
<!-- Detail Card -->
<div class="vh-card">
<div class="vh-grid">
<div class="vh-cell">
<div class="vh-cell__label">{{ 'labels.inputs.Name' | translate }}</div>
<div class="vh-cell__value">{{ holidayData.name }}</div>
</div>
<div class="vh-cell">
<div class="vh-cell__label">{{ 'labels.inputs.From Date' | translate }}</div>
<div class="vh-cell__value">{{ holidayData.fromDate | dateFormat }}</div>
</div>
<div class="vh-cell">
<div class="vh-cell__label">{{ 'labels.inputs.To Date' | translate }}</div>
<div class="vh-cell__value">{{ holidayData.toDate | dateFormat }}</div>
</div>
<div class="vh-cell">
<div class="vh-cell__label">{{ 'labels.inputs.Repayments Scheduled To' | translate }}</div>
<div class="vh-cell__value">
@if (holidayData.repaymentsRescheduledTo !== undefined && holidayData.repaymentsRescheduledTo !== null) {
{{ holidayData.repaymentsRescheduledTo | dateFormat }}
}
@if (holidayData.repaymentsRescheduledTo === undefined || holidayData.repaymentsRescheduledTo === null) {
{{ 'labels.inputs.Next Repayment Date' | translate }}
}
</div>
</div>
</div>
</div>
</div>
<!-- Actions menu -->
<mat-menu #actionMenu="matMenu">
<span *mifosxHasPermission="'ACTIVATE_HOLIDAY'">
@if (holidayData.status.value !== 'Active') {
<button mat-raised-button color="accent" (click)="activateHoliday()">
<fa-icon icon="lock-open" class="m-r-10"></fa-icon>
<button mat-menu-item (click)="activateHoliday()">
<fa-icon icon="lock-open" class="vh-menu-icon"></fa-icon>
{{ 'labels.buttons.Activate' | translate }}
</button>
}
</span>
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_HOLIDAY'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
<button mat-menu-item [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_HOLIDAY'">
<fa-icon icon="edit" class="vh-menu-icon"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
<button mat-raised-button color="warn" (click)="deleteHoliday()" *mifosxHasPermission="'DELETE_HOLIDAY'">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
<button mat-menu-item (click)="deleteHoliday()" *mifosxHasPermission="'DELETE_HOLIDAY'">
<fa-icon icon="trash" class="vh-menu-icon vh-menu-icon--danger"></fa-icon>
{{ 'labels.buttons.Delete' | translate }}
</button>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 header">
{{ 'labels.inputs.Name' | translate }}
</div>
<div class="flex-50">
{{ holidayData.name }}
</div>
<div class="flex-50 header">
{{ 'labels.inputs.From Date' | translate }}
</div>
<div class="flex-50">
{{ holidayData.fromDate | dateFormat }}
</div>
<div class="flex-50 header">
{{ 'labels.inputs.To Date' | translate }}
</div>
<div class="flex-50">
{{ holidayData.toDate | dateFormat }}
</div>
<div class="flex-50 header">
{{ 'labels.inputs.Repayments Scheduled To' | translate }}
</div>
@if (holidayData.repaymentsRescheduledTo !== undefined && holidayData.repaymentsRescheduledTo !== null) {
<div class="flex-50">
{{ holidayData.repaymentsRescheduledTo | dateFormat }}
</div>
}
@if (holidayData.repaymentsRescheduledTo === undefined || holidayData.repaymentsRescheduledTo === null) {
<div class="flex-50">
{{ 'labels.inputs.Next Repayment Date' | translate }}
</div>
}
</div>
</mat-card-content>
</mat-card>
</div>
</mat-menu>
@@ -6,17 +6,133 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.vh-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
&.header {
font-weight: 500;
}
}
// Header
.vh-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 20px;
flex-wrap: wrap;
&__left {
display: flex;
align-items: center;
gap: 14px;
}
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.vh-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.vh-menu-btn {
color: $gray-700 !important;
width: 36px !important;
height: 36px !important;
border: 1px solid $border;
border-radius: 8px !important;
&:hover {
background: $gray-50 !important;
border-color: $gray-500 !important;
}
}
// Card
.vh-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Grid
.vh-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
padding: 20px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.vh-cell {
padding: 14px 16px;
background: $gray-50;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.04);
transition: background 0.12s;
&:hover {
background: #f1f5f9;
}
&__label {
font-size: 10px;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
&__value {
font-size: 14px;
font-weight: 600;
color: #1e293b;
line-height: 1.4;
}
}
// Menu icons
.vh-menu-icon {
margin-right: 10px;
color: $accent;
font-size: 14px;
width: 18px;
text-align: center;
&--danger {
color: #ef4444;
}
}
// Responsive
@media (max-width: 600px) {
.vh-header {
flex-direction: column;
align-items: flex-start;
}
}
@@ -19,6 +19,8 @@ import { ConfirmationDialogComponent } from '../../../shared/confirmation-dialog
import { DeleteDialogComponent } from '../../../shared/delete-dialog/delete-dialog.component';
import { TranslateService } from '@ngx-translate/core';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatMenuModule } from '@angular/material/menu';
import { MatIconButton } from '@angular/material/button';
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@@ -32,6 +34,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatMenuModule,
MatIconButton,
DateFormatPipe
]
})
@@ -6,16 +6,30 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<mat-card class="container">
<mat-card-content>
<div class="investor-filter-section">
<div class="filter-row">
<mat-form-field class="filter-field">
<div class="inv-container">
<!-- Header -->
<div class="inv-header">
<button mat-stroked-button [routerLink]="['../']" class="inv-back">
<i class="fas fa-arrow-left"></i> Back
</button>
<h2 class="inv-header__title">Investors</h2>
</div>
<!-- Search Card -->
<div class="inv-card">
<!-- Section: Search -->
<div class="inv-section">
<div class="inv-section__header">
<i class="fas fa-search"></i>
<h3>Search</h3>
</div>
<div class="inv-grid">
<mat-form-field appearance="outline" class="inv-field">
<mat-label>{{ 'labels.text.Search by Text' | translate }}</mat-label>
<input matInput [formControl]="searchText" />
</mat-form-field>
<mat-form-field class="filter-field" (click)="effectiveFromDatePicker.open()">
<mat-form-field appearance="outline" class="inv-field" (click)="effectiveFromDatePicker.open()">
<mat-label>{{ 'labels.text.Effective Date From' | translate }}</mat-label>
<input
matInput
@@ -28,7 +42,7 @@
<mat-datepicker #effectiveFromDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field class="filter-field" (click)="effectiveToDatePicker.open()">
<mat-form-field appearance="outline" class="inv-field" (click)="effectiveToDatePicker.open()">
<mat-label>{{ 'labels.text.Effective Date To' | translate }}</mat-label>
<input
matInput
@@ -40,10 +54,8 @@
<mat-datepicker-toggle matSuffix [for]="effectiveToDatePicker"></mat-datepicker-toggle>
<mat-datepicker #effectiveToDatePicker></mat-datepicker>
</mat-form-field>
</div>
<div class="filter-row">
<mat-form-field class="filter-field" (click)="settlementFromDatePicker.open()">
<mat-form-field appearance="outline" class="inv-field" (click)="settlementFromDatePicker.open()">
<mat-label>{{ 'labels.text.Settlement Date From' | translate }}</mat-label>
<input
matInput
@@ -56,7 +68,7 @@
<mat-datepicker #settlementFromDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field class="filter-field" (click)="settlementToDatePicker.open()">
<mat-form-field appearance="outline" class="inv-field" (click)="settlementToDatePicker.open()">
<mat-label>{{ 'labels.text.Settlement Date To' | translate }}</mat-label>
<input
matInput
@@ -69,185 +81,153 @@
<mat-datepicker #settlementToDatePicker></mat-datepicker>
</mat-form-field>
<div class="filter-button-container">
<button mat-raised-button color="primary" (click)="searchEAO()" class="search-button">
<fa-icon icon="search" class="m-r-10"></fa-icon> {{ 'labels.buttons.Search' | translate }}
<div class="inv-search-btn-wrap">
<button mat-flat-button color="primary" (click)="searchEAO()" class="inv-search-btn">
<i class="fas fa-search"></i> {{ 'labels.buttons.Search' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8 container m-t-20">
@if (isLoading) {
<div class="loader">
<div class="loading"></div>
</div>
}
@if (existsDataToFilter) {
<mat-accordion>
@for (record of searchResults; track record; let i = $index) {
<mat-expansion-panel class="record">
<mat-expansion-panel-header>
<mat-panel-title>
<table>
<tr>
<td>
<div [ngClass]="itemStatus(record.status)" class="m-r-20">
<fa-icon icon="stop"></fa-icon>
<span class="m-l-10 status">{{ record.status }}</span>
</div>
</td>
<td class="field-name">{{ 'labels.inputs.Owner External Id' | translate }}</td>
<td>
<!-- Results Area -->
<div class="inv-section">
<div class="inv-section__header">
<i class="fas fa-list"></i>
<h3>Results</h3>
</div>
<div class="inv-results">
@if (isLoading) {
<div class="inv-loader">
<div class="inv-loader__spinner"></div>
</div>
}
@if (existsDataToFilter) {
<mat-accordion class="inv-accordion">
@for (record of searchResults; track record; let i = $index) {
<mat-expansion-panel class="inv-panel">
<mat-expansion-panel-header>
<mat-panel-title>
<div class="inv-panel-summary">
<div [ngClass]="itemStatus(record.status)" class="inv-status-badge">
<fa-icon icon="stop"></fa-icon>
<span class="inv-status-text">{{ record.status }}</span>
</div>
<div class="inv-panel-meta">
<span class="inv-panel-label">{{ 'labels.inputs.Owner External Id' | translate }}</span>
<mifosx-external-identifier
externalId="{{ record.owner.externalId }}"
></mifosx-external-identifier>
</td>
<td class="field-name">{{ 'labels.inputs.Transfer External Id' | translate }}</td>
<td>
</div>
<div class="inv-panel-meta">
<span class="inv-panel-label">{{ 'labels.inputs.Transfer External Id' | translate }}</span>
<mifosx-external-identifier
externalId="{{ record.transferExternalId }}"
></mifosx-external-identifier>
</td>
<td class="field-name">{{ 'labels.inputs.Effective From' | translate }}</td>
<td>{{ record.effectiveFrom | dateFormat }}</td>
</tr>
</table>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="external-asset-transfer">
<table>
<tbody>
<tr>
<td class="m-l-5 flex-25">
<b>{{ 'labels.inputs.Status' | translate }} :</b>
</td>
<td class="flex-25 m-l-5 left">
<div [ngClass]="itemStatus(record.status)">
<fa-icon icon="stop"></fa-icon>
<span class="m-l-5 status">{{ record.status }}</span>
</div>
<div class="inv-panel-meta">
<span class="inv-panel-label">{{ 'labels.inputs.Effective From' | translate }}</span>
<span>{{ record.effectiveFrom | dateFormat }}</span>
</div>
</div>
</mat-panel-title>
</mat-expansion-panel-header>
<!-- Expanded Content -->
<div class="inv-detail">
<div class="inv-detail__grid">
<div class="inv-detail__item">
<span class="inv-detail__label">{{ 'labels.inputs.Status' | translate }}</span>
<div [ngClass]="itemStatus(record.status)" class="inv-status-badge">
<fa-icon icon="stop"></fa-icon>
<span class="inv-status-text">{{ record.status }}</span>
</div>
</div>
<div class="inv-detail__item">
<span class="inv-detail__label">{{ 'labels.inputs.Owner External Id' | translate }}</span>
<mifosx-external-identifier
externalId="{{ record.owner.externalId }}"
completed="true"
></mifosx-external-identifier>
</div>
<div class="inv-detail__item">
<span class="inv-detail__label">{{ 'labels.inputs.Settlement Date' | translate }}</span>
<span class="inv-detail__value">{{ record.settlementDate | dateFormat }}</span>
</div>
<div class="inv-detail__item">
<span class="inv-detail__label">{{ 'labels.inputs.Effective Date' | translate }}</span>
<span class="inv-detail__value">{{ record.effectiveFrom | dateFormat }}</span>
</div>
</div>
@if (record.details) {
<div class="inv-financials">
<h4 class="inv-financials__title">{{ 'labels.inputs.Details' | translate }}</h4>
<div class="inv-financials__grid">
<div class="inv-financials__row">
<span class="inv-financials__label">{{ 'labels.inputs.Principal Outstanding' | translate }}</span>
<span class="inv-financials__amount">{{ record.details.totalPrincipalOutstanding | formatNumber }}</span>
</div>
</td>
<td class="m-l-5 flex-25">
<b>{{ 'labels.inputs.Owner External Id' | translate }} :</b>
</td>
<td class="flex-25 m-l-5 left">
<mifosx-external-identifier
externalId="{{ record.owner.externalId }}"
completed="true"
></mifosx-external-identifier>
</td>
</tr>
<tr>
<td class="m-l-5 flex-25">
<b>{{ 'labels.inputs.Settlement Date' | translate }} :</b>
</td>
<td class="m-l-5 flex-25">
{{ record.settlementDate | dateFormat }}
</td>
<td class="m-l-5 flex-25">
<b>{{ 'labels.inputs.Effective Date' | translate }} :</b>
</td>
<td class="m-l-5 flex-25">
{{ record.effectiveFrom | dateFormat }}
</td>
</tr>
@if (record.details) {
<tr>
<td class="m-l-5 flex-25">
<b>{{ 'labels.inputs.Details' | translate }} :</b>
</td>
<td class="flex-75 left">
<table>
<tbody>
<tr>
<td class="flex-50 m-l-5">
<b>{{ 'labels.inputs.Principal Outstanding' | translate }} :</b>
</td>
<td class="flex-50 r-amount">
{{ record.details.totalPrincipalOutstanding | formatNumber }}
</td>
</tr>
<tr>
<td class="flex-50 m-l-5">
<b>{{ 'labels.inputs.Interest Outstanding' | translate }} :</b>
</td>
<td class="flex-50 r-amount">
{{ record.details.totalInterestOutstanding | formatNumber }}
</td>
</tr>
<tr>
<td class="flex-50 m-l-5">
<b>{{ 'labels.inputs.Fees Outstanding' | translate }} :</b>
</td>
<td class="flex-50 r-amount">
{{ record.details.totalFeeChargesOutstanding | formatNumber }}
</td>
</tr>
<tr>
<td class="flex-50 m-l-5">
<b>{{ 'labels.inputs.Penalties Outstanding' | translate }} :</b>
</td>
<td class="flex-50 r-amount">
{{ record.details.totalPenaltyChargesOutstanding | formatNumber }}
</td>
</tr>
<tr>
<td class="flex-50 m-l-5">
<b>{{ 'labels.inputs.Outstanding' | translate }} :</b>
</td>
<td class="flex-50 r-amount">{{ record.details.totalOutstanding | formatNumber }}</td>
</tr>
<tr>
<td class="flex-50 m-l-5">
<b>{{ 'labels.inputs.Overpaid' | translate }} :</b>
</td>
<td class="flex-50 r-amount">{{ record.details.totalOverpaid | formatNumber }}</td>
</tr>
</tbody>
</table>
</td>
</tr>
}
</tbody>
</table>
</div>
<mat-divider [inset]="true"></mat-divider>
@if (canBeCancelled(record)) {
<div class="record-actions layout-row align-flex-end">
<button
mat-button
color="warn"
(click)="cancelPendingSale(record)"
matTooltip="{{ 'labels.text.Cancel Pending Sale' | translate }}"
>
<fa-icon icon="trash"></fa-icon>
</button>
<div class="inv-financials__row">
<span class="inv-financials__label">{{ 'labels.inputs.Interest Outstanding' | translate }}</span>
<span class="inv-financials__amount">{{ record.details.totalInterestOutstanding | formatNumber }}</span>
</div>
<div class="inv-financials__row">
<span class="inv-financials__label">{{ 'labels.inputs.Fees Outstanding' | translate }}</span>
<span class="inv-financials__amount">{{ record.details.totalFeeChargesOutstanding | formatNumber }}</span>
</div>
<div class="inv-financials__row">
<span class="inv-financials__label">{{ 'labels.inputs.Penalties Outstanding' | translate }}</span>
<span class="inv-financials__amount">{{ record.details.totalPenaltyChargesOutstanding | formatNumber }}</span>
</div>
<div class="inv-financials__row inv-financials__row--total">
<span class="inv-financials__label">{{ 'labels.inputs.Outstanding' | translate }}</span>
<span class="inv-financials__amount">{{ record.details.totalOutstanding | formatNumber }}</span>
</div>
<div class="inv-financials__row">
<span class="inv-financials__label">{{ 'labels.inputs.Overpaid' | translate }}</span>
<span class="inv-financials__amount">{{ record.details.totalOverpaid | formatNumber }}</span>
</div>
</div>
</div>
}
</div>
}
</mat-expansion-panel>
}
</mat-accordion>
}
@if (existsDataToFilter) {
<mat-paginator
[pageSize]="pageSize"
[pageSizeOptions]="[50, 100, 200]"
[length]="totalRows"
(page)="pageChanged($event)"
showFirstLastButtons
></mat-paginator>
}
</div>
<mat-divider [inset]="true"></mat-divider>
@if (canBeCancelled(record)) {
<div class="inv-panel-actions">
<button
mat-button
color="warn"
(click)="cancelPendingSale(record)"
matTooltip="{{ 'labels.text.Cancel Pending Sale' | translate }}"
>
<fa-icon icon="trash"></fa-icon>
</button>
</div>
}
</mat-expansion-panel>
}
</mat-accordion>
}
@if (!existsDataToFilter) {
<div class="alert">
<div class="message">
<i class="fa fa-exclamation-circle alert-check"></i>
{{ 'labels.text.No data found' | translate }}
</div>
@if (existsDataToFilter) {
<mat-paginator
[pageSize]="pageSize"
[pageSizeOptions]="[50, 100, 200]"
[length]="totalRows"
(page)="pageChanged($event)"
showFirstLastButtons
></mat-paginator>
}
@if (!existsDataToFilter) {
<div class="inv-empty">
<i class="fas fa-inbox inv-empty__icon"></i>
<p class="inv-empty__text">{{ 'labels.text.No data found' | translate }}</p>
</div>
}
</div>
}
</mat-card-content>
</mat-card>
</div>
</div>
</div>
@@ -6,198 +6,303 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Filter Section Styling
.investor-filter-section {
margin-bottom: 24px;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.filter-row {
.inv-container {
max-width: 960px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.inv-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.inv-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.inv-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.inv-section {
&__header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 12px;
flex-wrap: wrap;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
&:last-child {
margin-bottom: 0;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
.filter-field {
flex: 1 1 calc(33.333% - 11px);
min-width: 200px;
max-width: 100%;
// Ensure consistent height across all fields
::ng-deep .mat-mdc-form-field-infix {
min-height: 56px;
}
// Align datepicker icons consistently
::ng-deep .mat-datepicker-toggle {
display: flex;
align-items: center;
}
& + & {
border-top: 1px solid $gray-200;
}
}
.filter-button-container {
flex: 1 1 calc(33.333% - 11px);
min-width: 200px;
display: flex;
align-items: flex-end;
padding-bottom: 4px;
// Grid
.inv-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px 16px;
padding: 16px 24px;
.search-button {
width: 100%;
height: 48px;
font-size: 15px;
font-weight: 500;
letter-spacing: 0.5px;
@media (max-width: 768px) { grid-template-columns: 1fr; }
@media (max-width: 1024px) and (min-width: 769px) { grid-template-columns: repeat(2, 1fr); }
}
fa-icon {
margin-right: 8px;
.inv-field {
width: 100%;
}
.inv-search-btn-wrap {
display: flex;
align-items: center;
padding-top: 4px;
@media (max-width: 768px) { padding-top: 0; }
}
.inv-search-btn {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 44px !important;
width: 100%;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
// Results area
.inv-results {
padding: 16px 24px;
}
// Loader
.inv-loader {
display: flex;
justify-content: center;
padding: 40px 0;
&__spinner {
width: 36px;
height: 36px;
border: 3px solid $gray-200;
border-top-color: $accent;
border-radius: 50%;
animation: inv-spin 0.7s linear infinite;
}
}
@keyframes inv-spin {
to { transform: rotate(360deg); }
}
// Accordion & Panels
.inv-accordion {
::ng-deep .mat-expansion-panel {
border: 1px solid $border;
border-radius: 8px !important;
margin-bottom: 10px;
box-shadow: none !important;
&:not(.mat-expanded) {
&:hover {
border-color: #c5cdd8;
}
}
}
// Responsive adjustments for tablets
@media screen and (width <= 1024px) {
.filter-field,
.filter-button-container {
flex: 1 1 calc(50% - 8px);
min-width: 180px;
}
}
// Responsive adjustments for mobile
@media screen and (width <= 768px) {
.filter-row {
flex-direction: column;
gap: 12px;
}
.filter-field,
.filter-button-container {
flex: 1 1 100%;
width: 100%;
max-width: 100%;
min-width: 100%;
}
.filter-button-container {
padding-bottom: 0;
margin-top: 8px;
&.mat-expanded {
border-color: $accent;
}
}
}
.status {
color: black;
.inv-panel-summary {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
width: 100%;
}
.field-name {
font-weight: 800;
.inv-panel-meta {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 12px;
}
.record-actions {
margin-top: 1%;
.inv-panel-label {
font-size: 11px;
font-weight: 600;
color: $gray-500;
text-transform: uppercase;
letter-spacing: 0.3px;
}
button {
margin-right: 1%;
// Status badge
.inv-status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
font-weight: 600;
}
.inv-status-text {
color: $gray-900;
}
// Detail (expanded)
.inv-detail {
padding: 8px 0 16px;
&__grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 16px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
&__item {
display: flex;
flex-direction: column;
gap: 4px;
}
&__label {
font-size: 11px;
font-weight: 600;
color: $gray-500;
text-transform: uppercase;
letter-spacing: 0.3px;
}
&__value {
font-size: 13px;
color: $gray-900;
}
}
.external-asset-transfer {
margin-top: 10px;
min-height: 160px;
}
// Financials
.inv-financials {
background: $gray-50;
border: 1px solid $gray-200;
border-radius: 8px;
padding: 14px 18px;
.record {
.record-actions {
margin-top: 1%;
&__title {
margin: 0 0 12px;
font-size: 13px;
font-weight: 600;
color: $gray-900;
}
button {
margin-right: 1%;
&__grid {
display: flex;
flex-direction: column;
gap: 8px;
}
&__row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
&--total {
border-top: 1px solid $gray-200;
padding-top: 8px;
margin-top: 4px;
font-weight: 700;
}
}
h3 {
margin: 1% auto;
&__label {
font-size: 13px;
color: $gray-700;
}
p {
line-height: 30px;
margin-left: 2%;
&__amount {
font-size: 13px;
font-weight: 600;
color: $gray-900;
font-variant-numeric: tabular-nums;
}
}
$bar: 140px;
$foo: $bar * 8;
$color: #e2e4ec;
.loader {
margin-bottom: 39px;
// Panel actions
.inv-panel-actions {
display: flex;
justify-content: flex-end;
padding: 8px 0 0;
}
.sidenav {
overflow-x: hidden;
}
// Empty state
.inv-empty {
display: flex;
flex-direction: column;
align-items: center;
padding: 48px 0;
color: $gray-500;
@keyframes move {
0% {
left: 0;
&__icon {
font-size: 36px;
margin-bottom: 12px;
opacity: 0.5;
}
16.7% {
left: 0;
}
33.3% {
left: calc($foo / 2);
}
50% {
left: $foo - $bar;
}
66.7% {
left: calc($foo / 2);
}
83.3% {
left: 0;
}
100% {
left: 0;
}
}
@keyframes grow {
0% {
width: $bar;
}
16.7% {
width: calc($foo / 2);
}
33.3% {
width: calc($foo / 2);
}
50% {
width: $bar;
}
66.7% {
width: calc($foo / 2);
}
83.3% {
width: calc($foo / 2);
}
100% {
width: $bar;
&__text {
font-size: 14px;
margin: 0;
}
}
@@ -6,12 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="cl-container">
<div class="cl-header">
<button mat-stroked-button [routerLink]="['../']" class="cl-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Loan Originators' | translate }}
</button>
<h2 class="cl-header__title">{{ 'labels.heading.Create Loan Originator' | translate }}</h2>
</div>
<div class="cl-card">
<form [formGroup]="loanOriginatorForm" (ngSubmit)="submit()">
<mat-card-content>
<div #createLoanOriginatorFormRef class="layout-column">
<mat-form-field>
<div class="cl-section">
<div class="cl-section__header">
<i class="fas fa-handshake"></i>
<h3>{{ 'labels.heading.Loan Originator Details' | translate }}</h3>
</div>
<div class="cl-grid" #createLoanOriginatorFormRef>
<mat-form-field appearance="outline" class="cl-field">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (loanOriginatorForm.controls.name.hasError('required')) {
@@ -28,7 +39,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="cl-field">
<mat-label>{{ 'labels.inputs.External Id' | translate }}</mat-label>
<input matInput required formControlName="externalId" />
@if (loanOriginatorForm.controls.externalId.hasError('required')) {
@@ -45,7 +56,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="cl-field">
<mat-label>{{ 'labels.inputs.Status' | translate }}</mat-label>
<mat-select required formControlName="status">
@for (status of statusOptions; track status) {
@@ -62,7 +73,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="cl-field">
<mat-label>{{ 'labels.inputs.Originator Type' | translate }}</mat-label>
<mat-select formControlName="originatorTypeId">
@for (originatorType of originatorTypeOptions; track originatorType) {
@@ -79,7 +90,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="cl-field">
<mat-label>{{ 'labels.inputs.Channel Type' | translate }}</mat-label>
<mat-select formControlName="channelTypeId">
@for (channelTypeI of channelTypeOptions; track channelTypeI) {
@@ -96,21 +107,23 @@
}
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="cl-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="cl-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!loanOriginatorForm.valid"
class="cl-actions__submit"
*mifosxHasPermission="'CREATE_LOAN_ORIGINATOR'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,115 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.cl-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.cl-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.cl-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.cl-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.cl-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.cl-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.cl-field {
width: 100%;
}
.cl-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,12 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="el-container">
<div class="el-header">
<button mat-stroked-button [routerLink]="['../']" class="el-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Loan Originators' | translate }}
</button>
<h2 class="el-header__title">{{ 'labels.heading.Edit Loan Originator' | translate }}</h2>
</div>
<div class="el-card">
<form [formGroup]="loanOriginatorForm" (ngSubmit)="submit()">
<mat-card-content>
<div #editEmployeeFormRef class="layout-column">
<mat-form-field>
<div class="el-section">
<div class="el-section__header">
<i class="fas fa-handshake"></i>
<h3>{{ 'labels.heading.Loan Originator Details' | translate }}</h3>
</div>
<div class="el-grid">
<mat-form-field appearance="outline" class="el-field">
<mat-label>{{ 'labels.inputs.Name' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (loanOriginatorForm.controls.name.hasError('required')) {
@@ -28,7 +39,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="el-field">
<mat-label>{{ 'labels.inputs.External Id' | translate }}</mat-label>
<input matInput formControlName="externalId" readonly />
@if (loanOriginatorForm.controls.externalId.hasError('required')) {
@@ -45,7 +56,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="el-field">
<mat-label>{{ 'labels.inputs.Status' | translate }}</mat-label>
<mat-select required formControlName="status">
@for (status of statusOptions; track status) {
@@ -62,7 +73,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="el-field">
<mat-label>{{ 'labels.inputs.Originator Type' | translate }}</mat-label>
<mat-select formControlName="originatorTypeId">
@for (originatorType of originatorTypeOptions; track originatorType) {
@@ -79,7 +90,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="el-field">
<mat-label>{{ 'labels.inputs.Channel Type' | translate }}</mat-label>
<mat-select formControlName="channelTypeId">
@for (channelTypeI of channelTypeOptions; track channelTypeI) {
@@ -96,21 +107,23 @@
}
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="el-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="el-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!loanOriginatorForm.valid"
class="el-actions__submit"
*mifosxHasPermission="'UPDATE_LOAN_ORIGINATOR'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,115 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.el-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.el-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.el-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.el-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.el-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.el-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.el-field {
width: 100%;
}
.el-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,25 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<div #buttonCreateLoanOriginator>
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_LOAN_ORIGINATOR'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create' | translate }} {{ 'labels.inputs.Loan Originator' | translate }}
</button>
</div>
</div>
<mat-card class="container">
<!-- Header Bar -->
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-edit"></i>
{{ 'labels.heading.Manage Loan Originators' | translate }}
</h2>
</div>
<div class="container">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<input matInput #filter placeholder="{{ 'labels.inputs.Filter' | translate }}" (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" [routerLink]="['create']" class="btn-create" *mifosxHasPermission="'CREATE_LOAN_ORIGINATOR'" #buttonCreateLoanOriginator>
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Create' | translate }} {{ 'labels.inputs.Loan Originator' | translate }}
</button>
</div>
</div>
</div>
<div #tableLoanOriginators class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Table -->
<div class="table-container" #tableLoanOriginators>
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Id' | translate }}</th>
<td mat-cell *matCellDef="let loanOriginator">{{ loanOriginator.id }}</td>
@@ -65,6 +74,7 @@
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Channel Type' | translate }}</th>
<td mat-cell *matCellDef="let loanOriginator">{{ loanOriginator.channelType?.name }}</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let loanOriginator">
@@ -82,10 +92,11 @@
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</mat-card>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,63 +6,46 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_LOAN_ORIGINATOR'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="vl-container">
<div class="vl-header">
<button mat-stroked-button [routerLink]="['../']" class="vl-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Loan Originators' | translate }}
</button>
<h2 class="vl-header__title">{{ loanOriginatorData.name }}</h2>
<div class="vl-header__spacer"></div>
<button mat-flat-button color="primary" [routerLink]="['edit']" class="vl-header__edit" *mifosxHasPermission="'UPDATE_LOAN_ORIGINATOR'">
<i class="fas fa-pen"></i> {{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Name' | translate }}
<div class="vl-card">
<div class="vl-section">
<div class="vl-section__header">
<i class="fas fa-handshake"></i>
<h3>{{ 'labels.heading.Loan Originator Details' | translate }}</h3>
</div>
<div class="vl-details">
<div class="vl-detail">
<span class="vl-detail__label">{{ 'labels.inputs.Name' | translate }}</span>
<span class="vl-detail__value">{{ loanOriginatorData.name }}</span>
</div>
<div class="flex-50 wrap-content">
{{ loanOriginatorData.name }}
<div class="vl-detail">
<span class="vl-detail__label">{{ 'labels.inputs.External Id' | translate }}</span>
<span class="vl-detail__value">{{ loanOriginatorData.externalId }}</span>
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.External Id' | translate }}
<div class="vl-detail">
<span class="vl-detail__label">{{ 'labels.inputs.Status' | translate }}</span>
<span class="vl-detail__value">{{ loanOriginatorData.status }}</span>
</div>
<div class="flex-50 wrap-content">
{{ loanOriginatorData.externalId }}
<div class="vl-detail">
<span class="vl-detail__label">{{ 'labels.inputs.Originator Type' | translate }}</span>
<span class="vl-detail__value">{{ loanOriginatorData.originatorType?.name }}</span>
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Status' | translate }}
</div>
<div class="flex-50">
{{ loanOriginatorData.status }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Originator Type' | translate }}
</div>
<div class="flex-50">
{{ loanOriginatorData.originatorType?.name }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.Channel Type' | translate }}
</div>
<div class="flex-50">
{{ loanOriginatorData.channelType?.name }}
<div class="vl-detail">
<span class="vl-detail__label">{{ 'labels.inputs.Channel Type' | translate }}</span>
<span class="vl-detail__value">{{ loanOriginatorData.channelType?.name }}</span>
</div>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Back' | translate }}
</button>
</mat-card-actions>
</mat-card>
</div>
</div>
</div>
@@ -6,13 +6,117 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.vl-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.vl-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
&__spacer {
flex: 1;
}
&__edit {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
.vl-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.vl-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.vl-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.vl-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 20px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.vl-detail {
display: flex;
flex-direction: column;
gap: 4px;
&__label {
font-size: 12px;
font-weight: 600;
color: $gray-500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
&__value {
font-size: 14px;
color: $gray-900;
word-wrap: break-word;
}
}
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div>
<form [formGroup]="provisioningCriteriaForm">
<div class="layout-row gap-2percent">
<mat-form-field class="flex-fill">
<div class="cp-container">
<!-- Header -->
<div class="cp-header">
<button mat-stroked-button [routerLink]="['../']" class="cp-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Provisioning Criteria' | translate }}
</button>
<h2 class="cp-header__title">{{ 'labels.heading.Create Provisioning Criteria' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="cp-card">
<form [formGroup]="provisioningCriteriaForm">
<!-- Criteria Details Section -->
<div class="cp-section">
<div class="cp-section__header">
<i class="fas fa-edit"></i>
<h3>Criteria Details</h3>
</div>
<div class="cp-grid">
<mat-form-field appearance="outline" class="cp-field">
<mat-label>{{ 'labels.inputs.Provisioning Criteria' | translate }}</mat-label>
<input required matInput formControlName="criteriaName" />
@if (provisioningCriteriaForm.controls.criteriaName.hasError('required')) {
@@ -22,7 +36,7 @@
}
</mat-form-field>
<mat-form-field class="flex-fill">
<mat-form-field appearance="outline" class="cp-field">
<mat-label>{{ 'labels.inputs.Selected Products' | translate }}</mat-label>
<mat-select formControlName="loanProducts" multiple>
@for (product of loanProvisioningCriteriaTemplate.loanProducts; track product) {
@@ -39,77 +53,91 @@
}
</mat-form-field>
</div>
</form>
</div>
</div>
<div class="mat-elevation-z1">
@if (!provisioningCriteriaFormValid) {
<mat-hint> {{ 'labels.text.Provisioning criteria definitions' | translate }}</mat-hint>
}
<!-- Definitions Section -->
<div class="cp-section">
<div class="cp-section__header">
<i class="fas fa-table"></i>
<h3>Definitions</h3>
</div>
<div class="cp-table-wrap">
@if (!provisioningCriteriaFormValid) {
<mat-hint class="cp-hint">{{ 'labels.text.Provisioning criteria definitions' | translate }}</mat-hint>
}
<table mat-table class="m-t-10" [dataSource]="definitions">
<ng-container matColumnDef="category">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Category' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.categoryName }}</td>
</ng-container>
<div class="table-container">
<table mat-table class="modern-table" [dataSource]="definitions">
<ng-container matColumnDef="category">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Category' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.categoryName }}</td>
</ng-container>
<ng-container matColumnDef="minAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Min Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.minAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="minAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Min Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.minAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="maxAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Max Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.maxAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="maxAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Max Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.maxAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="percentage">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Percentage' | translate }} (%)</th>
<td mat-cell *matCellDef="let definition">{{ definition.provisioningPercentage || '0' }}</td>
</ng-container>
<ng-container matColumnDef="percentage">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Percentage' | translate }} (%)</th>
<td mat-cell *matCellDef="let definition">{{ definition.provisioningPercentage || '0' }}</td>
</ng-container>
<ng-container matColumnDef="liabilityAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Liability Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{
(definition.liabilityAccount | find: loanProvisioningCriteriaTemplate.glAccounts : 'id' : 'name') || '-'
}}
</td>
</ng-container>
<ng-container matColumnDef="liabilityAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Liability Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{
(definition.liabilityAccount | find: loanProvisioningCriteriaTemplate.glAccounts : 'id' : 'name') || '-'
}}
</td>
</ng-container>
<ng-container matColumnDef="expenseAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Expense Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{ (definition.expenseAccount | find: loanProvisioningCriteriaTemplate.glAccounts : 'id' : 'name') || '-' }}
</td>
</ng-container>
<ng-container matColumnDef="expenseAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Expense Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{ (definition.expenseAccount | find: loanProvisioningCriteriaTemplate.glAccounts : 'id' : 'name') || '-' }}
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let definition">
<button mat-button color="primary" (click)="editDefinition(definition)">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let definition">
<button mat-button color="primary" (click)="editDefinition(definition)">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</div>
</div>
<div class="m-t-20 layout-row layout-align-center gap-20px">
<button type="button" mat-raised-button [routerLink]="['../']">{{ 'labels.buttons.Cancel' | translate }}</button>
<button
mat-raised-button
color="primary"
[disabled]="!provisioningCriteriaFormValid"
(click)="submit()"
*mifosxHasPermission="'CREATE_PROVISIONING_CRITERIA'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</mat-card>
<!-- Actions -->
<div class="cp-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="cp-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
type="button"
mat-flat-button
color="primary"
[disabled]="!provisioningCriteriaFormValid"
(click)="submit()"
*mifosxHasPermission="'CREATE_PROVISIONING_CRITERIA'"
class="cp-actions__submit"
>
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
</div>
@@ -6,65 +6,138 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
padding: 0.5rem;
max-width: 1200px;
@use '../../../shared/styles/list-layout' as *;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.cp-container {
max-width: 900px;
margin: 0 auto;
padding: 20px 16px;
}
.layout-row {
// Header
.cp-header {
display: flex;
flex-direction: row;
}
mat-card {
padding: 1rem;
box-shadow: 0 2px 8px rgb(0 0 0 / 10%) !important;
border-radius: 8px;
.layout-row {
margin-bottom: 0.5rem;
}
}
.mat-elevation-z1 {
padding: 1rem;
margin-top: 0.5rem;
border-radius: 4px;
}
table {
width: 100%;
th {
font-weight: 600;
}
}
.m-t-20 {
margin-top: 2rem;
}
.layout-align-center {
align-items: center;
justify-content: center;
}
gap: 14px;
margin-bottom: 20px;
@media (width <= 768px) {
.container {
padding: 0.5rem;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
mat-card {
padding: 1rem;
.cp-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.cp-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.cp-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
.layout-row {
flex-direction: column;
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.cp-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.cp-field {
width: 100%;
}
// Table wrap
.cp-table-wrap {
padding: 16px 24px;
}
.cp-hint {
display: block;
font-size: 12px;
color: $gray-500;
margin-bottom: 10px;
}
// Actions
.cp-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
.gap-20px {
flex-direction: column;
width: 100%;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div>
<form [formGroup]="provisioningCriteriaForm">
<div class="layout-row gap-2percent">
<mat-form-field class="flex-fill">
<div class="ep-container">
<!-- Header -->
<div class="ep-header">
<button mat-stroked-button [routerLink]="['../']" class="ep-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Provisioning Criteria' | translate }}
</button>
<h2 class="ep-header__title">{{ 'labels.heading.Edit Provisioning Criteria' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="ep-card">
<form [formGroup]="provisioningCriteriaForm">
<!-- Criteria Details Section -->
<div class="ep-section">
<div class="ep-section__header">
<i class="fas fa-edit"></i>
<h3>Criteria Details</h3>
</div>
<div class="ep-grid">
<mat-form-field appearance="outline" class="ep-field">
<mat-label>{{ 'labels.inputs.Provisioning Criteria' | translate }}</mat-label>
<input required matInput formControlName="criteriaName" />
@if (provisioningCriteriaForm.controls.criteriaName.hasError('required')) {
@@ -22,7 +36,7 @@
}
</mat-form-field>
<mat-form-field class="flex-fill">
<mat-form-field appearance="outline" class="ep-field">
<mat-label>{{ 'labels.inputs.Selected Products' | translate }}</mat-label>
<mat-select formControlName="loanProducts" [compareWith]="compareOptions" multiple>
@for (product of loanProducts; track product) {
@@ -33,80 +47,94 @@
</mat-select>
</mat-form-field>
</div>
</form>
</div>
</div>
<div class="mat-elevation-z1">
@if (!provisioningCriteriaFormValid) {
<mat-hint>{{ 'labels.text.Provisioning criteria definitions' | translate }}</mat-hint>
}
<!-- Definitions Section -->
<div class="ep-section">
<div class="ep-section__header">
<i class="fas fa-table"></i>
<h3>Definitions</h3>
</div>
<div class="ep-table-wrap">
@if (!provisioningCriteriaFormValid) {
<mat-hint class="ep-hint">{{ 'labels.text.Provisioning criteria definitions' | translate }}</mat-hint>
}
<table mat-table class="m-t-10" [dataSource]="definitions">
<ng-container matColumnDef="category">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Category' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.categoryName }}</td>
</ng-container>
<div class="table-container">
<table mat-table class="modern-table" [dataSource]="definitions">
<ng-container matColumnDef="category">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Category' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.categoryName }}</td>
</ng-container>
<ng-container matColumnDef="minAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Min Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.minAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="minAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Min Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.minAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="maxAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Max Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.maxAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="maxAge">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Max Age' | translate }}</th>
<td mat-cell *matCellDef="let definition">{{ definition.maxAge || '0' }}</td>
</ng-container>
<ng-container matColumnDef="percentage">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Percentage' | translate }} (%)</th>
<td mat-cell *matCellDef="let definition">{{ definition.provisioningPercentage || '0' }}</td>
</ng-container>
<ng-container matColumnDef="percentage">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Percentage' | translate }} (%)</th>
<td mat-cell *matCellDef="let definition">{{ definition.provisioningPercentage || '0' }}</td>
</ng-container>
<ng-container matColumnDef="liabilityAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Liability Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{
(definition.liabilityAccount | find: loanProvisioningCriteriaAndTemplate.glAccounts : 'id' : 'name') ||
'-'
}}
</td>
</ng-container>
<ng-container matColumnDef="liabilityAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Liability Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{
(definition.liabilityAccount | find: loanProvisioningCriteriaAndTemplate.glAccounts : 'id' : 'name') ||
'-'
}}
</td>
</ng-container>
<ng-container matColumnDef="expenseAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Expense Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{
(definition.expenseAccount | find: loanProvisioningCriteriaAndTemplate.glAccounts : 'id' : 'name') || '-'
}}
</td>
</ng-container>
<ng-container matColumnDef="expenseAccount">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Expense Account' | translate }}</th>
<td mat-cell *matCellDef="let definition">
{{
(definition.expenseAccount | find: loanProvisioningCriteriaAndTemplate.glAccounts : 'id' : 'name') || '-'
}}
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let definition">
<button mat-button color="primary" (click)="editDefinition(definition)">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</td>
</ng-container>
<ng-container matColumnDef="edit">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let definition">
<button mat-button color="primary" (click)="editDefinition(definition)">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</div>
</div>
<div class="m-t-20 layout-row layout-align-center gap-20px">
<button type="button" mat-raised-button [routerLink]="['../']">{{ 'labels.buttons.Cancel' | translate }}</button>
<button
mat-raised-button
color="primary"
[disabled]="!provisioningCriteriaFormValid"
(click)="submit()"
*mifosxHasPermission="'UPDATE_CRITERIA'"
>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</mat-card>
<!-- Actions -->
<div class="ep-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ep-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
type="button"
mat-flat-button
color="primary"
[disabled]="!provisioningCriteriaFormValid"
(click)="submit()"
*mifosxHasPermission="'UPDATE_CRITERIA'"
class="ep-actions__submit"
>
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</form>
</div>
</div>
@@ -5,3 +5,139 @@
* 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/.
*/
@use '../../../shared/styles/list-layout' as *;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.ep-container {
max-width: 900px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.ep-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ep-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.ep-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.ep-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.ep-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ep-field {
width: 100%;
}
// Table wrap
.ep-table-wrap {
padding: 16px 24px;
}
.ep-hint {
display: block;
font-size: 12px;
color: $gray-500;
margin-bottom: 10px;
}
// Actions
.ep-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,28 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button
mat-raised-button
color="primary"
[routerLink]="['create']"
*mifosxHasPermission="'CREATE_PROVISIONING_CRITERIA'"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Provisioning Criteria' | translate }}
</button>
</div>
<mat-card class="container">
<!-- Header Bar -->
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-edit"></i>
{{ 'labels.heading.Loan Provisioning Criteria' | translate }}
</h2>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<input matInput placeholder="{{ 'labels.inputs.Filter' | translate }}" (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" [routerLink]="['create']" class="btn-create" *mifosxHasPermission="'CREATE_PROVISIONING_CRITERIA'">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Create Provisioning Criteria' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Table -->
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="criteriaName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let loanProvisioningCriteria">{{ loanProvisioningCriteria.criteriaName }}</td>
@@ -39,14 +45,9 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
[routerLink]="[row.criteriaId]"
class="select-row"
></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.criteriaId]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</mat-card>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,12 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="cf-container">
<div class="cf-header">
<button mat-stroked-button [routerLink]="['../']" class="cf-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Manage Funds' | translate }}
</button>
<h2 class="cf-header__title">{{ 'labels.heading.Create Fund' | translate }}</h2>
</div>
<div class="cf-card">
<form [formGroup]="fundForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<div class="cf-section">
<div class="cf-section__header">
<i class="fas fa-wallet"></i>
<h3>{{ 'labels.heading.Fund Details' | translate }}</h3>
</div>
<div class="cf-grid">
<mat-form-field appearance="outline" class="cf-field">
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required autofocus formControlName="name" />
@if (fundForm.controls.name.hasError('required')) {
@@ -22,21 +33,21 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="cf-field">
<mat-label>{{ 'labels.inputs.External Id' | translate }}</mat-label>
<input matInput autofocus formControlName="externalId" />
<input matInput formControlName="externalId" />
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="cf-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="cf-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!fundForm.valid" *mifosxHasPermission="'CREATE_FUND'">
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="!fundForm.valid" class="cf-actions__submit" *mifosxHasPermission="'CREATE_FUND'">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,115 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.cf-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.cf-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.cf-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.cf-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.cf-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.cf-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.cf-field {
width: 100%;
}
.cf-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,12 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="ef-container">
<div class="ef-header">
<button mat-stroked-button [routerLink]="['../']" class="ef-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Manage Funds' | translate }}
</button>
<h2 class="ef-header__title">{{ 'labels.heading.Edit Fund' | translate }}</h2>
</div>
<div class="ef-card">
<form [formGroup]="fundForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<div class="ef-section">
<div class="ef-section__header">
<i class="fas fa-wallet"></i>
<h3>{{ 'labels.heading.Fund Details' | translate }}</h3>
</div>
<div class="ef-grid">
<mat-form-field appearance="outline" class="ef-field">
<mat-label>{{ 'labels.inputs.name' | translate }}</mat-label>
<input matInput required autofocus formControlName="name" />
@if (fundForm.controls.name.hasError('required')) {
@@ -22,21 +33,21 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="ef-field">
<mat-label>{{ 'labels.inputs.External Id' | translate }}</mat-label>
<input matInput autofocus formControlName="externalId" />
<input matInput formControlName="externalId" />
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="ef-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ef-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!fundForm.valid" *mifosxHasPermission="'UPDATE_FUND'">
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="!fundForm.valid" class="ef-actions__submit" *mifosxHasPermission="'UPDATE_FUND'">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,115 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.ef-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.ef-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ef-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.ef-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.ef-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.ef-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.ef-field {
width: 100%;
}
.ef-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,42 +6,52 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<div #fundFormRef class="in-block">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_FUND'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Fund' | translate }}
</button>
</div>
</div>
<div class="container">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-money-bill-alt title-icon"></i>
{{ 'labels.heading.Manage Funds' | translate }}
</h2>
</div>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<div #fundFormRef class="in-block">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_FUND'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Fund' | translate }}
</button>
</div>
</div>
</div>
</div>
<div #chargesTable class="mat-elevation-z8 table-container">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let charge">{{ charge.name }}</td>
</ng-container>
<div #filter class="table-container">
<div #chargesTable>
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let charge">{{ charge.name }}</td>
</ng-container>
<ng-container matColumnDef="externalId">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.External Id' | translate }}</th>
<td mat-cell *matCellDef="let charge">
<mifosx-external-identifier externalId="{{ charge.externalId }}"></mifosx-external-identifier>
</td>
</ng-container>
<ng-container matColumnDef="externalId">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.External Id' | translate }}</th>
<td mat-cell *matCellDef="let charge">
<mifosx-external-identifier externalId="{{ charge.externalId }}"></mifosx-external-identifier>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="select-row"></tr>
</table>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</div>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,42 +6,39 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row align-end gap-2px responsive-column container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_FUND'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="vf-container">
<div class="vf-header">
<button mat-stroked-button [routerLink]="['../']" class="vf-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Manage Funds' | translate }}
</button>
<h2 class="vf-header__title">{{ fundData.name }}</h2>
<div class="vf-header__spacer"></div>
<button mat-flat-button color="primary" [routerLink]="['edit']" class="vf-header__edit" *mifosxHasPermission="'UPDATE_FUND'">
<i class="fas fa-pen"></i> {{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.name' | translate }}
<div class="vf-card">
<div class="vf-section">
<div class="vf-section__header">
<i class="fas fa-wallet"></i>
<h3>{{ 'labels.heading.Fund Details' | translate }}</h3>
</div>
<div class="vf-details">
<div class="vf-detail">
<span class="vf-detail__label">{{ 'labels.inputs.name' | translate }}</span>
<span class="vf-detail__value">{{ fundData.name }}</span>
</div>
<div class="flex-50">
{{ fundData.name }}
</div>
<div class="flex-50 mat-body-strong">
{{ 'labels.inputs.External Id' | translate }}
</div>
<div class="flex-50">
<mifosx-external-identifier
externalId="{{ fundData.externalId }}"
completed="true"
></mifosx-external-identifier>
<div class="vf-detail">
<span class="vf-detail__label">{{ 'labels.inputs.External Id' | translate }}</span>
<span class="vf-detail__value">
<mifosx-external-identifier
externalId="{{ fundData.externalId }}"
completed="true"
></mifosx-external-identifier>
</span>
</div>
</div>
<div class="layout-row layout-align-center gap-2percent column-on-mobile">
<button type="button" mat-raised-button color="primary" [routerLink]="['../']">
{{ 'labels.buttons.Back' | translate }}
</button>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
</div>
@@ -6,13 +6,117 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
.vf-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.vf-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
&__spacer {
flex: 1;
}
&__edit {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
.vf-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.vf-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.vf-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
}
.vf-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 20px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.vf-detail {
display: flex;
flex-direction: column;
gap: 4px;
&__label {
font-size: 12px;
font-weight: 600;
color: $gray-500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
&__value {
font-size: 14px;
color: $gray-900;
word-wrap: break-word;
}
}
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="co-container">
<!-- Header -->
<div class="co-header">
<button mat-stroked-button [routerLink]="['../']" class="co-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Offices' | translate }}
</button>
<h2 class="co-header__title">{{ 'labels.heading.Create Office' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="co-card">
<form [formGroup]="officeForm" (ngSubmit)="submit()">
<mat-card-content>
<div #createOfficeFormRef class="layout-column">
<mat-form-field>
<!-- Office Details Section -->
<div class="co-section">
<div class="co-section__header">
<i class="fas fa-building"></i>
<h3>{{ 'labels.heading.Office Details' | translate }}</h3>
</div>
<div class="co-grid" #createOfficeFormRef>
<mat-form-field appearance="outline" class="co-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<input required matInput formControlName="name" />
@if (officeForm.controls.name.hasError('required')) {
@@ -22,7 +36,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="co-field">
<mat-label>{{ 'labels.inputs.Parent Office' | translate }}</mat-label>
<mat-select required formControlName="parentId">
@for (office of officeData; track office) {
@@ -39,7 +53,7 @@
}
</mat-form-field>
<mat-form-field (click)="openedOnDatePicker.open()">
<mat-form-field appearance="outline" class="co-field" (click)="openedOnDatePicker.open()">
<mat-label>{{ 'labels.inputs.Opened On' | translate }}</mat-label>
<input
matInput
@@ -59,23 +73,24 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="co-field">
<mat-label>{{ 'labels.inputs.External ID' | translate }}</mat-label>
<input matInput formControlName="externalId" />
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<!-- Actions -->
<div class="co-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="co-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!officeForm.valid" *mifosxHasPermission="'CREATE_OFFICE'">
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="!officeForm.valid" class="co-actions__submit" *mifosxHasPermission="'CREATE_OFFICE'">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
<ng-template #templateCreateOfficeForm let-popover="popover">
@@ -6,6 +6,128 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.co-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.co-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.co-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.co-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.co-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.co-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.co-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
// Actions
.co-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,12 +6,26 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="eo-container">
<!-- Header -->
<div class="eo-header">
<button mat-stroked-button [routerLink]="['../general']" class="eo-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Office' | translate }}
</button>
<h2 class="eo-header__title">{{ 'labels.heading.Edit Office' | translate }}</h2>
</div>
<!-- Form Card -->
<div class="eo-card">
<form [formGroup]="officeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<!-- Office Details Section -->
<div class="eo-section">
<div class="eo-section__header">
<i class="fas fa-building"></i>
<h3>{{ 'labels.heading.Office Details' | translate }}</h3>
</div>
<div class="eo-grid">
<mat-form-field appearance="outline" class="eo-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<input matInput required formControlName="name" />
@if (officeForm.controls.name.hasError('required')) {
@@ -23,7 +37,7 @@
</mat-form-field>
@if (officeForm.contains('parentId')) {
<mat-form-field>
<mat-form-field appearance="outline" class="eo-field">
<mat-label>{{ 'labels.inputs.Parent Office' | translate }}</mat-label>
<mat-select required formControlName="parentId">
@for (office of officeData.allowedParents; track office) {
@@ -41,7 +55,7 @@
</mat-form-field>
}
<mat-form-field (click)="openingDatePicker.open()">
<mat-form-field appearance="outline" class="eo-field" (click)="openingDatePicker.open()">
<mat-label>{{ 'labels.inputs.Opened On' | translate }}</mat-label>
<input
matInput
@@ -61,21 +75,22 @@
}
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="eo-field">
<mat-label>{{ 'labels.inputs.External ID' | translate }}</mat-label>
<input matInput formControlName="externalId" />
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../general']">
<!-- Actions -->
<div class="eo-actions">
<button type="button" mat-stroked-button [routerLink]="['../general']" class="eo-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" [disabled]="!officeForm.valid" *mifosxHasPermission="'UPDATE_OFFICE'">
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="!officeForm.valid" class="eo-actions__submit" *mifosxHasPermission="'UPDATE_OFFICE'">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,128 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.eo-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
// Header
.eo-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.eo-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
// Card
.eo-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Section
.eo-section {
&__header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 24px;
background: $gray-50;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: $gray-900;
}
}
& + & {
border-top: 1px solid $gray-200;
}
}
// Grid
.eo-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.eo-field {
width: 100%;
&--full {
grid-column: 1 / -1;
}
}
// Actions
.eo-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
@@ -6,178 +6,177 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<mat-button-toggle-group #buttonTreeView [formControl]="viewGroup">
<mat-button-toggle value="listView">
<fa-icon icon="list"></fa-icon>
</mat-button-toggle>
<mat-button-toggle value="treeView">
<fa-icon icon="sitemap"></fa-icon>
</mat-button-toggle>
</mat-button-toggle-group>
<div #buttonCreateOffice class="in-block">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_OFFICE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Office' | translate }}
</button>
</div>
<div #buttonImportOffices class="in-block">
<button
mat-raised-button
color="primary"
*mifosxHasPermission="'CREATE_OFFICE'"
[routerLink]="['/organization', 'bulk-import', 'Offices']"
>
<fa-icon icon="upload" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Import Offices' | translate }}
</button>
</div>
</div>
<div class="container" [hidden]="viewGroup.value !== 'listView'">
<div #filter class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="container">
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-building title-icon"></i>
{{ 'labels.heading.Manage Offices' | translate }}
</h2>
</div>
<div class="table-actions">
<div class="table-search">
<mat-form-field appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'Filter' | translate }}</mat-label>
<input #filter matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
</div>
<mat-button-toggle-group #buttonTreeView [formControl]="viewGroup">
<mat-button-toggle value="listView">
<fa-icon icon="list"></fa-icon>
</mat-button-toggle>
<mat-button-toggle value="treeView">
<fa-icon icon="sitemap"></fa-icon>
</mat-button-toggle>
</mat-button-toggle-group>
<div class="table-buttons">
<div #buttonImportOffices>
<button mat-stroked-button class="btn-import" *mifosxHasPermission="'CREATE_OFFICE'" [routerLink]="['/organization', 'bulk-import', 'Offices']">
<fa-icon icon="upload" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Import Offices' | translate }}
</button>
</div>
<div #buttonCreateOffice>
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_OFFICE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Office' | translate }}
</button>
</div>
</div>
</div>
</div>
<!-- List View -->
<div #tableOffices class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' Office Name ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.name }}</td>
</ng-container>
<div [hidden]="viewGroup.value !== 'listView'">
<div #tableOffices class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' Office Name ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.name }}</td>
</ng-container>
<ng-container matColumnDef="externalId">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' External ID ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.externalId }}</td>
</ng-container>
<ng-container matColumnDef="externalId">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' External ID ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.externalId }}</td>
</ng-container>
<ng-container matColumnDef="parentName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' Parent Office ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.parentName }}</td>
</ng-container>
<ng-container matColumnDef="parentName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' Parent Office ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.parentName }}</td>
</ng-container>
<ng-container matColumnDef="openingDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' Opened On ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.openingDate | dateFormat }}</td>
</ng-container>
<ng-container matColumnDef="openingDate">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ ' Opened On ' | translate }}</th>
<td mat-cell *matCellDef="let offices">{{ offices.openingDate | dateFormat }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
class="select-row"
[routerLink]="[row.id, 'general']"
></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
<!-- Tree View -->
<div class="container" [hidden]="viewGroup.value !== 'treeView'">
<div class="m-b-20 layout-row align-start gap-20px">
<button mat-raised-button (click)="toggleExpandCollapse()">
{{ (isTreeExpanded ? 'labels.buttons.Collapse All' : 'labels.buttons.Expand All') | translate }}
</button>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row" [routerLink]="[row.id, 'general']"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
<div class="layout-row gap-4percent responsive-column">
<div class="flex-fill">
<mat-card>
<mat-card-content>
<mat-tree [dataSource]="nestedTreeDataSource" [treeControl]="nestedTreeControl" class="office-tree">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
<li class="mat-tree-node">
<button mat-icon-button disabled></button>
<span (click)="viewOfficeNode(node)" class="m-r-10">
{{ node.name }}
</span>
</li>
</mat-tree-node>
<!-- Tree View -->
<div [hidden]="viewGroup.value !== 'treeView'">
<div class="m-b-20 layout-row align-start gap-20px">
<button mat-raised-button (click)="toggleExpandCollapse()">
{{ (isTreeExpanded ? 'labels.buttons.Collapse All' : 'labels.buttons.Expand All') | translate }}
</button>
</div>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<li>
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name">
<fa-icon
class="mat-icon-rtl-mirror"
icon="{{ nestedTreeControl.isExpanded(node) ? 'chevron-down' : 'chevron-right' }}"
></fa-icon>
</button>
<div class="layout-row gap-4percent responsive-column">
<div class="flex-fill">
<mat-card>
<mat-card-content>
<mat-tree [dataSource]="nestedTreeDataSource" [treeControl]="nestedTreeControl" class="office-tree">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
<li class="mat-tree-node">
<button mat-icon-button disabled></button>
<span (click)="viewOfficeNode(node)" class="m-r-10">
{{ node.name }}
</span>
</div>
<ul [class.office-tree-invisible]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
</mat-card-content>
</mat-card>
</div>
</li>
</mat-tree-node>
@if (office) {
<div class="flex-48">
<mat-card>
<mat-card-content>
<div class="layout-row">
<mat-card-title>
{{ office.name }}
</mat-card-title>
<div class="flex align-end">
<button mat-icon-button (click)="closeOffice()">
<fa-icon icon="times"></fa-icon>
</button>
</div>
</div>
<mat-tab-group>
<mat-tab label="General">
<div class="layout-row-wrap">
@if (office.parentId) {
<div class="flex-50 mat-body-strong">
{{ 'Parent Office' | translate }}
</div>
}
@if (office.parentId) {
<div class="flex-50">
{{ office.parentName }}
</div>
}
<div class="flex-50 mat-body-strong">
{{ ' Opened On' | translate }}
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<li>
<div class="mat-tree-node">
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name">
<fa-icon
class="mat-icon-rtl-mirror"
icon="{{ nestedTreeControl.isExpanded(node) ? 'chevron-down' : 'chevron-right' }}"
></fa-icon>
</button>
<span (click)="viewOfficeNode(node)" class="m-r-10">
{{ node.name }}
</span>
</div>
<div class="flex-50">
{{ office.openingDate | date }}
</div>
@if (office.externalId) {
<div class="flex-50 mat-body-strong">
{{ 'External Id' | translate }}
</div>
}
@if (office.externalId) {
<div class="flex-50">
<mifosx-external-identifier externalId="{{ office.externalId }}"></mifosx-external-identifier>
</div>
}
</div>
</mat-tab>
@for (dataTable of dataTablesData; track dataTable) {
<mat-tab label="{{ dataTable.registeredTableName }}"></mat-tab>
}
</mat-tab-group>
<ul [class.office-tree-invisible]="!nestedTreeControl.isExpanded(node)">
<ng-container matTreeNodeOutlet></ng-container>
</ul>
</li>
</mat-nested-tree-node>
</mat-tree>
</mat-card-content>
</mat-card>
</div>
}
@if (office) {
<div class="flex-48">
<mat-card>
<mat-card-content>
<div class="layout-row">
<mat-card-title>
{{ office.name }}
</mat-card-title>
<div class="flex align-end">
<button mat-icon-button (click)="closeOffice()">
<fa-icon icon="times"></fa-icon>
</button>
</div>
</div>
<mat-tab-group>
<mat-tab label="General">
<div class="layout-row-wrap">
@if (office.parentId) {
<div class="flex-50 mat-body-strong">
{{ 'Parent Office' | translate }}
</div>
}
@if (office.parentId) {
<div class="flex-50">
{{ office.parentName }}
</div>
}
<div class="flex-50 mat-body-strong">
{{ ' Opened On' | translate }}
</div>
<div class="flex-50">
{{ office.openingDate | date }}
</div>
@if (office.externalId) {
<div class="flex-50 mat-body-strong">
{{ 'External Id' | translate }}
</div>
}
@if (office.externalId) {
<div class="flex-50">
<mifosx-external-identifier externalId="{{ office.externalId }}"></mifosx-external-identifier>
</div>
}
</div>
</mat-tab>
@for (dataTable of dataTablesData; track dataTable) {
<mat-tab label="{{ dataTable.registeredTableName }}"></mat-tab>
}
</mat-tab-group>
</mat-card-content>
</mat-card>
</div>
}
</div>
</div>
</div>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,48 +6,33 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="office-card-wrapper">
<div class="card-header">
<span class="card-title">
{{ 'labels.heading.General' | translate }}
</span>
</div>
<div class="gt-detail">
<div class="gt-grid">
<div class="gt-cell">
<div class="gt-cell__label">{{ 'labels.inputs.Parent Office' | translate }}</div>
<div class="gt-cell__value">{{ officeData.parentName ? officeData.parentName : 'N/A' }}</div>
</div>
<div class="tab-container mat-typography compact-view">
<div class="layout-row-wrap responsive-column compact-details condensed">...</div>
</div>
</div>
<div class="tab-container mat-typography compact-view">
<div class="layout-row-wrap responsive-column compact-details condensed">
<div class="flex-45 mat-body-strong left">{{ 'labels.inputs.Parent Office' | translate }}</div>
<div class="flex-50 right">
{{ officeData.parentName ? officeData.parentName : 'N/A' }}
</div>
<hr class="section-divider" />
<div class="flex-45 mat-body-strong left">{{ 'labels.inputs.Opened On' | translate }}</div>
<div class="flex-50 right">
{{ officeData.openingDate ? (officeData.openingDate | dateFormat) : 'Unassigned' }}
<div class="gt-cell">
<div class="gt-cell__label">{{ 'labels.inputs.Opened On' | translate }}</div>
<div class="gt-cell__value">{{ officeData.openingDate ? (officeData.openingDate | dateFormat) : 'Unassigned' }}</div>
</div>
<hr class="section-divider" />
<div class="flex-45 mat-body-strong left">{{ 'labels.inputs.Name Decorated' | translate }}</div>
<div class="flex-50 right">
{{ officeData.nameDecorated ? officeData.nameDecorated : 'Unassigned' }}
<div class="gt-cell">
<div class="gt-cell__label">{{ 'labels.inputs.Name Decorated' | translate }}</div>
<div class="gt-cell__value">{{ officeData.nameDecorated ? officeData.nameDecorated : 'Unassigned' }}</div>
</div>
<hr class="section-divider" />
<div class="flex-45 mat-body-strong left">{{ 'labels.inputs.External Id' | translate }}</div>
@if (officeData.externalId) {
<div class="flex-50 right">
<mifosx-external-identifier externalId="{{ officeData.externalId }}"></mifosx-external-identifier>
<div class="gt-cell">
<div class="gt-cell__label">{{ 'labels.inputs.External Id' | translate }}</div>
<div class="gt-cell__value">
@if (officeData.externalId) {
<mifosx-external-identifier externalId="{{ officeData.externalId }}"></mifosx-external-identifier>
}
@if (!officeData.externalId) {
{{ 'labels.inputs.Unassigned' | translate }}
}
</div>
}
@if (!officeData.externalId) {
<div class="flex-50 right">
{{ 'labels.inputs.Unassigned' | translate }}
</div>
}
</div>
</div>
</div>
@@ -6,124 +6,52 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.office-card-wrapper {
max-width: 450px;
margin: 0 auto;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.gt-detail {
padding: 4px 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 0.75rem 0.25rem;
// Grid
.gt-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
@media (max-width: 600px) { grid-template-columns: 1fr; }
}
.card-title {
font-weight: 600;
font-size: 1rem;
}
.tab-container {
padding: 0.5rem;
margin: 1% auto;
max-width: 600px;
width: 90%;
.delete-button {
margin-left: 1%;
}
}
.table-name {
padding-left: 2%;
}
.table-data {
margin-top: 3px;
}
.compact-button {
padding: 0.25rem 0.75rem;
font-size: 0.9rem;
line-height: 1.75;
}
.compact-details {
font-size: 0.9rem;
.left,
.right {
padding: 0.25rem 0;
}
.mat-body-strong {
font-size: 0.9rem;
}
}
.bottom-button-container {
display: flex;
justify-content: center;
margin-top: 2rem;
padding: 1rem 0;
}
.edit-button {
min-width: 120px;
padding: 0.5rem 1.5rem;
font-size: 1rem;
border-radius: 4px;
box-shadow: 0 3px 5px rgb(0 0 0 / 20%);
transition: all 0.3s ease;
.gt-cell {
padding: 14px 16px;
background: $gray-50;
border-radius: 8px;
border: 1px solid rgba(0, 0, 0, 0.04);
transition: background 0.12s;
&:hover {
box-shadow: 0 4px 8px rgb(0 0 0 / 30%);
transform: translateY(-2px);
}
}
.layout-row-wrap {
margin: 0 auto;
padding: 0.5rem;
}
.section-divider {
width: 100%;
border: 0;
border-top: 1px solid rgb(0 0 0 / 10%);
margin: 8px 0;
}
.compact-view {
padding: 0.3rem;
margin: 0 auto;
max-width: 450px;
width: 90%;
}
.condensed {
.flex-45,
.flex-50 {
padding: 0.2rem 0;
font-size: 0.85rem;
background: #f1f5f9;
}
.section-divider {
margin: 4px 0;
&__label {
font-size: 10px;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
&__value {
font-size: 14px;
font-weight: 600;
color: #1e293b;
line-height: 1.4;
}
}
.small-buttons {
margin-top: 1rem;
.edit-button {
min-width: 100px;
padding: 0.35rem 1rem;
font-size: 0.9rem;
}
}
.tab-container.compact-view {
max-width: 450px;
width: 90%;
}
@@ -6,53 +6,53 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container narrow-container extra-small">
<div class="action-bar">
<button
mat-raised-button
color="primary"
class="edit-button"
[routerLink]="['./edit']"
*mifosxHasPermission="'UPDATE_OFFICE'"
>
<div class="vo-container">
<!-- Header -->
<div class="vo-header">
<div class="vo-header__left">
<button mat-stroked-button [routerLink]="['../../']" class="vo-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.inputs.Offices' | translate }}
</button>
<h2 class="vo-header__title">{{ 'labels.heading.Office Details' | translate }}</h2>
</div>
<button mat-flat-button color="primary" [routerLink]="['./edit']" class="vo-edit-btn" *mifosxHasPermission="'UPDATE_OFFICE'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
</div>
<mat-card class="office-card">
<mat-card-content class="card-content">
<nav mat-tab-nav-bar class="navigation-tabs" [tabPanel]="tabPanel">
<a
mat-tab-link
[routerLink]="['./general']"
routerLinkActive
#general="routerLinkActive"
[active]="general.isActive"
class="compact-tab"
>
{{ 'labels.inputs.General' | translate }}
</a>
@for (officeDatatable of officeDatatables; track officeDatatable) {
<span>
<a
mat-tab-link
*mifosxHasPermission="'READ_' + officeDatatable.registeredTableName"
[routerLink]="['./datatables', officeDatatable.registeredTableName]"
routerLinkActive
#datatable="routerLinkActive"
[active]="datatable.isActive"
class="compact-tab"
>
{{ officeDatatable.registeredTableName }}
</a>
</span>
}
</nav>
<!-- Tab Card -->
<div class="vo-card">
<nav mat-tab-nav-bar class="vo-tabs" [tabPanel]="tabPanel">
<a
mat-tab-link
[routerLink]="['./general']"
routerLinkActive
#general="routerLinkActive"
[active]="general.isActive"
class="vo-tab"
>
{{ 'labels.inputs.General' | translate }}
</a>
@for (officeDatatable of officeDatatables; track officeDatatable) {
<span>
<a
mat-tab-link
*mifosxHasPermission="'READ_' + officeDatatable.registeredTableName"
[routerLink]="['./datatables', officeDatatable.registeredTableName]"
routerLinkActive
#datatable="routerLinkActive"
[active]="datatable.isActive"
class="vo-tab"
>
{{ officeDatatable.registeredTableName }}
</a>
</span>
}
</nav>
<mat-tab-nav-panel #tabPanel class="tab-panel">
<router-outlet></router-outlet>
</mat-tab-nav-panel>
</mat-card-content>
</mat-card>
<mat-tab-nav-panel #tabPanel class="vo-panel">
<router-outlet></router-outlet>
</mat-tab-nav-panel>
</div>
</div>
@@ -6,104 +6,95 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.action-button {
width: 85%;
margin: 0.2rem auto;
padding: 0.3rem;
border-radius: 3px;
transition: all 0.3s ease;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
}
}
.office-card {
width: 100%;
margin: 0.75rem auto;
padding: 0.5rem;
border-radius: 8px !important;
box-shadow: 0 2px 8px rgb(0 0 0 / 10%) !important;
.navigation-tabs {
overflow: auto;
padding: 0.2rem 0;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(0 0 0 / 20%);
border-radius: 4px;
}
.mat-tab-label {
min-width: 80px;
padding: 0 0.5rem;
height: 36px;
font-size: 0.85rem;
}
}
@media (width <= 768px) {
width: 85%;
padding: 0.4rem;
}
}
.container {
padding: 0.5rem;
}
.narrow-container {
max-width: 600px;
.vo-container {
max-width: 800px;
margin: 0 auto;
padding: 0.75rem;
padding: 20px 16px;
}
.extra-small {
max-width: 500px;
// Header
.vo-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 20px;
flex-wrap: wrap;
&__left {
display: flex;
align-items: center;
gap: 14px;
}
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.compact-tab {
.vo-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.vo-edit-btn {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
}
// Card
.vo-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
// Tabs
.vo-tabs {
padding: 0 24px;
border-bottom: 1px solid $gray-200;
}
.vo-tab {
min-width: auto;
padding: 0 12px;
padding: 0 16px;
font-size: 13px;
font-weight: 500;
}
.card-content {
padding: 0.5rem;
// Panel
.vo-panel {
padding: 16px 24px;
}
.tab-panel {
padding: 0.5rem 0;
}
.action-bar {
display: flex;
justify-content: flex-end;
align-items: center;
max-width: 600px;
margin: 0 auto 0.5rem;
padding: 0 0.5rem;
}
.edit-button {
min-width: 110px;
padding: 0.4rem 1rem;
font-size: 0.95rem;
}
.tab-header-with-actions {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0.5rem 0.5rem 0;
margin-bottom: 0.5rem;
}
.tab-actions {
display: flex;
gap: 0.5rem;
// Responsive
@media (max-width: 600px) {
.vo-header {
flex-direction: column;
align-items: flex-start;
}
}
+280 -543
View File
@@ -6,552 +6,289 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="layout-row responsive-column gap-20px">
<div class="flex-50">
<mat-nav-list>
<div #office>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['offices']">
<mat-icon matListIcon>
<fa-icon icon="building" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Manage Offices' | translate }}
@if (arrowBooleans[0]) {
<p matLine [routerLink]="['offices']" class="menu-explanation">
{{ 'labels.text.Add new office or modify or deactivate office' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[0]) {
<fa-icon (click)="arrowBooleansToggle(0)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[0]) {
<fa-icon (click)="arrowBooleansToggle(0)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #holidays>
<mat-list-item *mifosxHasPermission="'READ_HOLIDAY'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['holidays']">
<mat-icon matListIcon>
<fa-icon icon="calendar-alt" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Manage Holidays' | translate }}
@if (arrowBooleans[1]) {
<p matLine [routerLink]="['holidays']" class="menu-explanation">
{{ 'labels.text.Define holidays for office' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[1]) {
<fa-icon (click)="arrowBooleansToggle(1)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[1]) {
<fa-icon (click)="arrowBooleansToggle(1)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #employee>
<mat-list-item *mifosxHasPermission="'READ_STAFF'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['employees']">
<mat-icon matListIcon>
<fa-icon icon="user" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Manage Employees' | translate }}
@if (arrowBooleans[2]) {
<p matLine [routerLink]="['employees']" class="menu-explanation">
{{ 'labels.text.Employee represents loan officers' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[2]) {
<fa-icon (click)="arrowBooleansToggle(2)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[2]) {
<fa-icon (click)="arrowBooleansToggle(2)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #standinginstructions>
<mat-list-item *mifosxHasPermission="'READ_STANDINGINSTRUCTION'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['standing-instructions-history']">
<mat-icon matListIcon>
<fa-icon icon="book" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Standing Instructions History' | translate }}
@if (arrowBooleans[3]) {
<p matLine [routerLink]="['standing-instructions-history']" class="menu-explanation">
{{ 'labels.text.View logged history' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[3]) {
<fa-icon (click)="arrowBooleansToggle(3)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[3]) {
<fa-icon (click)="arrowBooleansToggle(3)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #investor>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['investors']">
<mat-icon>
<fa-icon icon="user-tie" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.text.Investors' | translate }}
@if (arrowBooleans[4]) {
<p matLine [routerLink]="['investors']" class="menu-explanation">
{{ 'labels.text.View the loan account details associated with the investors' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[4]) {
<fa-icon (click)="arrowBooleansToggle(4)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[4]) {
<fa-icon (click)="arrowBooleansToggle(4)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #fundmapping>
<mat-list-item *mifosxHasPermission="'READ_OFFICE'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['fund-mapping']">
<mat-icon matListIcon>
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Fund Mapping' | translate }}
@if (arrowBooleans[5]) {
<p matLine [routerLink]="['fund-mapping']" class="menu-explanation">
{{ 'labels.text.Bulk entry screen for mapping' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[5]) {
<fa-icon (click)="arrowBooleansToggle(5)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[5]) {
<fa-icon (click)="arrowBooleansToggle(5)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #passwordpreferences>
<mat-list-item *mifosxHasPermission="'READ_PASSWORD_VALIDATION_POLICY'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['password-preferences']">
<mat-icon>
<fa-icon icon="lock" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Password Preferences' | translate }}
@if (arrowBooleans[6]) {
<p matLine [routerLink]="['password-preferences']" class="menu-explanation">
{{ 'labels.text.The usage of stronger passwords' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[6]) {
<fa-icon (click)="arrowBooleansToggle(6)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[6]) {
<fa-icon (click)="arrowBooleansToggle(6)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #provisioning>
<mat-list-item *mifosxHasPermission="'VIEW_PROVISIONS'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['provisioning-criteria']">
<mat-icon matListIcon>
<fa-icon icon="edit" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Loan Provisioning Criteria' | translate }}
@if (arrowBooleans[7]) {
<p matLine [routerLink]="['provisioning-criteria']" class="menu-explanation">
{{ 'labels.text.Loan Provisioning Criteria Organization' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[7]) {
<fa-icon (click)="arrowBooleansToggle(7)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[7]) {
<fa-icon (click)="arrowBooleansToggle(7)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #entitydatatable>
<mat-list-item *mifosxHasPermission="'READ_ENTITY_DATATABLE_CHECK'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['entity-data-table-checks']">
<mat-icon matListIcon>
<fa-icon icon="check" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Entity Data Table Checks' | translate }}
@if (arrowBooleans[8]) {
<p matLine [routerLink]="['entity-data-table-checks']" class="menu-explanation">
{{ 'labels.text.Entity Data Table Checks Organization' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[8]) {
<fa-icon (click)="arrowBooleansToggle(8)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[8]) {
<fa-icon (click)="arrowBooleansToggle(8)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
</mat-nav-list>
</div>
<div class="flex-50">
<mat-nav-list>
<div #addEditCurrency>
<mat-list-item *mifosxHasPermission="'READ_CURRENCY'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['currencies']">
<mat-icon matListIcon>
<fa-icon icon="cogs" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Currency Configuration' | translate }}
@if (arrowBooleans[9]) {
<p matLine [routerLink]="['currencies']" class="menu-explanation">
{{ 'labels.text.Currencies available across organization' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[9]) {
<fa-icon (click)="arrowBooleansToggle(9)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[9]) {
<fa-icon (click)="arrowBooleansToggle(9)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #manageFunds>
<mat-list-item *mifosxHasPermission="'READ_FUND'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['manage-funds']">
<mat-icon matListIcon>
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Manage Funds' | translate }}
@if (arrowBooleans[10]) {
<p matLine [routerLink]="['manage-funds']" class="menu-explanation">
{{ 'labels.text.Funds are associated with loans' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[10]) {
<fa-icon (click)="arrowBooleansToggle(10)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[10]) {
<fa-icon (click)="arrowBooleansToggle(10)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #manageLoanOriginators>
<mat-list-item *mifosxHasPermission="'READ_LOAN_ORIGINATOR'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['manage-loan-originators']">
<mat-icon matListIcon>
<fa-icon icon="edit" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Manage Loan Originators' | translate }}
@if (arrowBooleans[18]) {
<p matLine [routerLink]="['manage-loan-originators']" class="menu-explanation">
{{ 'labels.text.Loan Originators are associated with loan accounts' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[18]) {
<fa-icon (click)="arrowBooleansToggle(18)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[18]) {
<fa-icon (click)="arrowBooleansToggle(18)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #bulkloan>
<mat-list-item *mifosxHasPermission="'BULKREASSIGN_LOAN'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['bulkloan']">
<mat-icon>
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Bulk Loan Reassignment' | translate }}
@if (arrowBooleans[11]) {
<p matLine [routerLink]="['bulkloan']" class="menu-explanation">
{{ 'labels.text.Easy way to reassign all the loan' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[11]) {
<fa-icon (click)="arrowBooleansToggle(11)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[11]) {
<fa-icon (click)="arrowBooleansToggle(11)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #tellers>
<mat-list-item *mifosxHasPermission="'READ_TELLER'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['tellers']">
<mat-icon>
<fa-icon icon="money-bill-alt" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Teller / Cashier Management' | translate }}
@if (arrowBooleans[12]) {
<p matLine [routerLink]="['tellers']" class="menu-explanation">
{{ 'labels.text.Manage Tellers / Cashiers' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[12]) {
<fa-icon (click)="arrowBooleansToggle(12)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[12]) {
<fa-icon (click)="arrowBooleansToggle(12)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #workingDays>
<mat-list-item *mifosxHasPermission="'READ_WORKINGDAYS'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['working-days']">
<mat-icon>
<fa-icon icon="calendar-alt" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Working Days' | translate }}
@if (arrowBooleans[13]) {
<p matLine [routerLink]="['working-days']" class="menu-explanation">
{{ 'labels.text.Working days and configure behaviour of payments' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[13]) {
<fa-icon (click)="arrowBooleansToggle(13)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[13]) {
<fa-icon (click)="arrowBooleansToggle(13)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #paymenttypes>
<mat-list-item *mifosxHasPermission="'READ_PAYMENTTYPE'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['payment-types']">
<mat-icon matListIcon>
<fa-icon icon="dollar-sign" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Payment Type' | translate }}
@if (arrowBooleans[14]) {
<p matLine [routerLink]="['payment-types']" class="menu-explanation">
{{ 'labels.text.Manage payment types' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[14]) {
<fa-icon (click)="arrowBooleansToggle(14)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[14]) {
<fa-icon (click)="arrowBooleansToggle(14)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #smscampaigns>
<mat-list-item *mifosxHasPermission="'VIEW_SMSCAMPAIGNS'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['sms-campaigns']">
<mat-icon matListIcon>
<fa-icon icon="envelope" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.SMS Campaigns' | translate }}
@if (arrowBooleans[15]) {
<p matLine [routerLink]="['sms-campaigns']" class="menu-explanation">
{{ 'labels.text.Define SMS Campaigns for Organization' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[15]) {
<fa-icon (click)="arrowBooleansToggle(15)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[15]) {
<fa-icon (click)="arrowBooleansToggle(15)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #adhocquery>
<mat-list-item *mifosxHasPermission="'VIEW_ADHOC'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['adhoc-query']">
<mat-icon matListIcon>
<fa-icon icon="chevron-right" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.AdHocQuery' | translate }}
@if (arrowBooleans[16]) {
<p matLine [routerLink]="['adhoc-query']" class="menu-explanation">
{{ 'labels.text.Define AdHocQuery for Organization' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[16]) {
<fa-icon (click)="arrowBooleansToggle(16)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[16]) {
<fa-icon (click)="arrowBooleansToggle(16)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
<div #bulkimport>
<mat-list-item *mifosxHasPermission="'VIEW_BULKIMPORT'">
<div class="menu-list-item-content">
<div class="menu-left-section" [routerLink]="['bulk-import']">
<mat-icon matListIcon>
<fa-icon icon="upload" size="sm"></fa-icon>
</mat-icon>
<div matLine>
{{ 'labels.heading.Bulk Import' | translate }}
@if (arrowBooleans[17]) {
<p matLine [routerLink]="['bulk-import']" class="menu-explanation">
{{ 'labels.text.Bulk data import using excel spreadsheet templates' | translate }}
</p>
}
</div>
</div>
<div class="menu-right-section">
@if (!arrowBooleans[17]) {
<fa-icon (click)="arrowBooleansToggle(17)" icon="arrow-down" size="md"></fa-icon>
}
@if (arrowBooleans[17]) {
<fa-icon (click)="arrowBooleansToggle(17)" icon="arrow-up" size="md"></fa-icon>
}
</div>
</div>
</mat-list-item>
</div>
</mat-nav-list>
</div>
<div class="org-container">
<!-- Page header -->
<div class="org-header">
<div class="org-header__icon">
<i class="fas fa-sitemap"></i>
</div>
</mat-card>
<div>
<h1 class="org-header__title">{{ 'labels.heading.Organization' | translate }}</h1>
<p class="org-header__sub">Manage offices, employees, currencies and other organizational settings</p>
</div>
</div>
<!-- Card Grid -->
<div class="org-grid">
<!-- Manage Offices -->
<div #office>
<a class="org-card" [routerLink]="['offices']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--blue">
<i class="fas fa-building"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Manage Offices' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Add new office or modify or deactivate office' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Currency Configuration -->
<div #addEditCurrency>
<a class="org-card" [routerLink]="['currencies']" *mifosxHasPermission="'READ_CURRENCY'">
<div class="org-card__icon org-card__icon--amber">
<i class="fas fa-cogs"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Currency Configuration' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Currencies available across organization' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Manage Holidays -->
<div #holidays>
<a class="org-card" [routerLink]="['holidays']" *mifosxHasPermission="'READ_HOLIDAY'">
<div class="org-card__icon org-card__icon--green">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Manage Holidays' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Define holidays for office' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Manage Funds -->
<div #manageFunds>
<a class="org-card" [routerLink]="['manage-funds']" *mifosxHasPermission="'READ_FUND'">
<div class="org-card__icon org-card__icon--emerald">
<i class="fas fa-money-bill-alt"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Manage Funds' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Funds are associated with loans' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Manage Employees -->
<div #employee>
<a class="org-card" [routerLink]="['employees']" *mifosxHasPermission="'READ_STAFF'">
<div class="org-card__icon org-card__icon--purple">
<i class="fas fa-user"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Manage Employees' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Employee represents loan officers' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Manage Loan Originators -->
<div #manageLoanOriginators>
<a class="org-card" [routerLink]="['manage-loan-originators']" *mifosxHasPermission="'READ_LOAN_ORIGINATOR'">
<div class="org-card__icon org-card__icon--indigo">
<i class="fas fa-edit"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Manage Loan Originators' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Loan Originators are associated with loan accounts' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Standing Instructions History -->
<div #standinginstructions>
<a class="org-card" [routerLink]="['standing-instructions-history']" *mifosxHasPermission="'READ_STANDINGINSTRUCTION'">
<div class="org-card__icon org-card__icon--slate">
<i class="fas fa-book"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Standing Instructions History' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.View logged history' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Bulk Loan Reassignment -->
<div #bulkloan>
<a class="org-card" [routerLink]="['bulkloan']" *mifosxHasPermission="'BULKREASSIGN_LOAN'">
<div class="org-card__icon org-card__icon--orange">
<i class="fas fa-money-bill-alt"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Bulk Loan Reassignment' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Easy way to reassign all the loan' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Investors -->
<div #investor>
<a class="org-card" [routerLink]="['investors']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--teal">
<i class="fas fa-user-tie"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.text.Investors' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.View the loan account details associated with the investors' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Teller / Cashier Management -->
<div #tellers>
<a class="org-card" [routerLink]="['tellers']" *mifosxHasPermission="'READ_TELLER'">
<div class="org-card__icon org-card__icon--cyan">
<i class="fas fa-cash-register"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Teller / Cashier Management' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Manage Tellers / Cashiers' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Fund Mapping -->
<div #fundmapping>
<a class="org-card" [routerLink]="['fund-mapping']" *mifosxHasPermission="'READ_OFFICE'">
<div class="org-card__icon org-card__icon--rose">
<i class="fas fa-money-bill-alt"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Fund Mapping' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Bulk entry screen for mapping' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Working Days -->
<div #workingDays>
<a class="org-card" [routerLink]="['working-days']" *mifosxHasPermission="'READ_WORKINGDAYS'">
<div class="org-card__icon org-card__icon--green">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Working Days' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Working days and configure behaviour of payments' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Password Preferences -->
<div #passwordpreferences>
<a class="org-card" [routerLink]="['password-preferences']" *mifosxHasPermission="'READ_PASSWORD_VALIDATION_POLICY'">
<div class="org-card__icon org-card__icon--red">
<i class="fas fa-lock"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Password Preferences' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.The usage of stronger passwords' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Payment Type -->
<div #paymenttypes>
<a class="org-card" [routerLink]="['payment-types']" *mifosxHasPermission="'READ_PAYMENTTYPE'">
<div class="org-card__icon org-card__icon--emerald">
<i class="fas fa-dollar-sign"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Payment Type' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Manage payment types' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Loan Provisioning Criteria -->
<div #provisioning>
<a class="org-card" [routerLink]="['provisioning-criteria']" *mifosxHasPermission="'VIEW_PROVISIONS'">
<div class="org-card__icon org-card__icon--indigo">
<i class="fas fa-edit"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Loan Provisioning Criteria' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Loan Provisioning Criteria Organization' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- SMS Campaigns -->
<div #smscampaigns>
<a class="org-card" [routerLink]="['sms-campaigns']" *mifosxHasPermission="'VIEW_SMSCAMPAIGNS'">
<div class="org-card__icon org-card__icon--purple">
<i class="fas fa-envelope"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.SMS Campaigns' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Define SMS Campaigns for Organization' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Entity Data Table Checks -->
<div #entitydatatable>
<a class="org-card" [routerLink]="['entity-data-table-checks']" *mifosxHasPermission="'READ_ENTITY_DATATABLE_CHECK'">
<div class="org-card__icon org-card__icon--slate">
<i class="fas fa-check"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Entity Data Table Checks' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Entity Data Table Checks Organization' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- AdHocQuery -->
<div #adhocquery>
<a class="org-card" [routerLink]="['adhoc-query']" *mifosxHasPermission="'VIEW_ADHOC'">
<div class="org-card__icon org-card__icon--amber">
<i class="fas fa-chevron-right"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.AdHocQuery' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Define AdHocQuery for Organization' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
<!-- Bulk Import -->
<div #bulkimport>
<a class="org-card" [routerLink]="['bulk-import']" *mifosxHasPermission="'VIEW_BULKIMPORT'">
<div class="org-card__icon org-card__icon--blue">
<i class="fas fa-upload"></i>
</div>
<div class="org-card__body">
<h3 class="org-card__title">{{ 'labels.heading.Bulk Import' | translate }}</h3>
<p class="org-card__desc">{{ 'labels.text.Bulk data import using excel spreadsheet templates' | translate }}</p>
</div>
<i class="fas fa-chevron-right org-card__arrow"></i>
</a>
</div>
</div>
</div>
<!-- Configuration Wizard Templates (unchanged) -->
<ng-template #templateOffice let-popover="popover">
<h2>{{ 'labels.heading.Manage Offices' | translate }}</h2>
<p class="mw300">{{ 'labels.text.Manage Offices page allows you to create, edit' | translate }}</p>
@@ -5,3 +5,212 @@
* 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/.
*/
$border: #e2e8f0;
$gray-50: #f8fafc;
$gray-100: #f1f5f9;
$gray-400: #94a3b8;
$gray-500: #64748b;
$gray-700: #334155;
$gray-900: #0f172a;
// ── Container ──
.org-container {
max-width: 1100px;
margin: 0 auto;
padding: 24px 20px;
}
// ── Page Header ──
.org-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 28px;
&__icon {
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, #0d3d78, #1a5276);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 18px;
flex-shrink: 0;
}
&__title {
font-size: 20px;
font-weight: 700;
color: $gray-900;
margin: 0;
letter-spacing: -0.3px;
}
&__sub {
font-size: 13px;
color: $gray-500;
margin: 2px 0 0;
}
}
// ── Card Grid ──
.org-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}
// ── Individual Card ──
.org-card {
display: flex;
align-items: center;
gap: 14px;
padding: 14px 16px;
background: #fff;
border: 1px solid $border;
border-radius: 10px;
text-decoration: none;
cursor: pointer;
transition: all 0.15s ease;
&:hover {
border-color: #cbd5e1;
background: $gray-50;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
.org-card__arrow {
color: $gray-700;
transform: translateX(2px);
}
}
&__icon {
width: 40px;
height: 40px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
flex-shrink: 0;
&--blue {
background: rgba(59, 130, 246, 0.1);
color: #2563eb;
}
&--amber {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
&--green {
background: rgba(34, 197, 94, 0.1);
color: #16a34a;
}
&--emerald {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
&--purple {
background: rgba(139, 92, 246, 0.1);
color: #7c3aed;
}
&--indigo {
background: rgba(99, 102, 241, 0.1);
color: #4f46e5;
}
&--slate {
background: rgba(100, 116, 139, 0.1);
color: #475569;
}
&--orange {
background: rgba(249, 115, 22, 0.1);
color: #ea580c;
}
&--teal {
background: rgba(20, 184, 166, 0.1);
color: #0d9488;
}
&--cyan {
background: rgba(6, 182, 212, 0.1);
color: #0891b2;
}
&--rose {
background: rgba(244, 63, 94, 0.1);
color: #e11d48;
}
&--red {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
}
&__body {
flex: 1;
min-width: 0;
}
&__title {
font-size: 13.5px;
font-weight: 600;
color: $gray-900;
margin: 0;
line-height: 1.3;
}
&__desc {
font-size: 11.5px;
color: $gray-400;
margin: 3px 0 0;
line-height: 1.35;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__arrow {
font-size: 11px;
color: #cbd5e1;
flex-shrink: 0;
transition: all 0.15s ease;
}
}
// ── Dark mode ──
:host-context(.dark-theme) {
.org-header {
&__title { color: #e2e8f0; }
&__sub { color: #94a3b8; }
}
.org-card {
background: #1e1e1e;
border-color: #333;
&:hover {
background: #262626;
border-color: #444;
}
&__title { color: #e2e8f0; }
&__desc { color: #64748b; }
&__arrow { color: #444; }
}
}
+1 -16
View File
@@ -13,10 +13,6 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
/** Custom Services */
import { ConfigurationWizardService } from '../configuration-wizard/configuration-wizard.service';
import { PopoverService } from '../configuration-wizard/popover/popover.service';
import { MatNavList, MatListItem } from '@angular/material/list';
import { MatIcon } from '@angular/material/icon';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatLine } from '@angular/material/grid-list';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -27,12 +23,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
templateUrl: './organization.component.html',
styleUrls: ['./organization.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatNavList,
MatListItem,
MatIcon,
FaIconComponent,
MatLine
...STANDALONE_SHARED_IMPORTS
]
})
export class OrganizationComponent implements AfterViewInit {
@@ -66,8 +57,6 @@ export class OrganizationComponent implements AfterViewInit {
@ViewChild('manageFunds') manageFunds: ElementRef<any>;
/* Template for popover on manage funds */
@ViewChild('templateManageFunds') templateManageFunds: TemplateRef<any>;
// Initialize an array of 18 boolean values, all set to false
arrowBooleans: boolean[] = new Array(19).fill(false);
/**
* Popover function
@@ -226,8 +215,4 @@ export class OrganizationComponent implements AfterViewInit {
this.router.navigate(['/products/recurring-deposit-products']);
}
arrowBooleansToggle(arrowNumber: number) {
// Toggle the boolean value at the given index
this.arrowBooleans[arrowNumber] = !this.arrowBooleans[arrowNumber];
}
}
@@ -1,49 +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="pp-container">
<div class="pp-header">
<button mat-stroked-button [routerLink]="['../']" class="pp-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
</button>
<h2 class="pp-header__title">{{ 'labels.heading.Password Preferences' | translate }}</h2>
</div>
<div class="container">
<mat-card>
<div class="pp-card">
<form [formGroup]="passwordPreferencesForm" (ngSubmit)="submit()">
<mat-card-content>
<mat-radio-group class="password-cards-container" formControlName="validationPolicyId">
<div
*ngFor="let passwordPreference of passwordPreferencesData; trackBy: trackByPasswordPreference"
class="password-card"
[class.selected]="passwordPreferencesForm.get('validationPolicyId')?.value === passwordPreference.id"
>
<div class="card-header">
<div class="card-heading">
{{ getPasswordLabel(passwordPreference) | translate }}
<div class="pp-section">
<div class="pp-section__header">
<i class="fas fa-lock"></i>
<h3>Password Validation Policy</h3>
</div>
<div class="pp-body">
<mat-radio-group class="pp-options" formControlName="validationPolicyId">
<div *ngFor="let passwordPreference of passwordPreferencesData; trackBy: trackByPasswordPreference"
class="pp-option"
[class.pp-option--selected]="passwordPreferencesForm.get('validationPolicyId')?.value === passwordPreference.id">
<div class="pp-option__header">
<div class="pp-option__title">{{ getPasswordLabel(passwordPreference) | translate }}</div>
<mat-radio-button [value]="passwordPreference.id"></mat-radio-button>
</div>
<mat-radio-button [value]="passwordPreference.id"></mat-radio-button>
<div class="pp-option__desc">{{ passwordPreference.description | translateKey: 'passwordPreferences' }}</div>
</div>
</mat-radio-group>
</div>
</div>
<div class="card-description">
<span class="description-wrap">
{{ passwordPreference.description | translateKey: 'passwordPreferences' }}
</span>
</div>
</div>
</mat-radio-group>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="pp-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="pp-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
color="primary"
[disabled]="passwordPreferencesForm.pristine"
*mifosxHasPermission="'UPDATE_PASSWORD_VALIDATION_POLICY'"
>
{{ 'labels.buttons.Submit' | translate }}
<button type="submit" mat-flat-button color="primary" [disabled]="passwordPreferencesForm.pristine"
*mifosxHasPermission="'UPDATE_PASSWORD_VALIDATION_POLICY'" class="pp-actions__submit">
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -5,49 +5,57 @@
* 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/.
*/
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.description-wrap {
white-space: normal;
.pp-container { max-width: 700px; margin: 0 auto; padding: 20px 16px; }
.pp-header {
display: flex; align-items: center; gap: 14px; margin-bottom: 20px;
&__title { font-size: 18px; font-weight: 600; color: $gray-900; margin: 0; }
}
.password-cards-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
width: 100%;
.pp-back {
border-radius: 8px !important; font-size: 12px !important; font-weight: 600 !important;
height: 32px !important; border-color: $border !important; color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.password-card {
width: 100%;
border: 1px solid rgb(0 0 0 / 12%);
border-radius: 4px;
padding: 1.5rem;
box-sizing: border-box;
.pp-card { background: #fff; border: 1px solid $border; border-radius: 10px; overflow: hidden; }
&.selected {
border-color: currentcolor;
border-width: 2px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.card-heading {
font-weight: 500;
font-size: 1rem;
}
.card-description {
padding-left: 0;
.description-wrap {
white-space: normal;
display: block;
line-height: 1.5;
}
.pp-section {
&__header {
display: flex; align-items: center; gap: 10px; padding: 12px 24px;
background: $gray-50; border-bottom: 1px solid $gray-200;
i { font-size: 14px; color: $accent; }
h3 { margin: 0; font-size: 14px; font-weight: 600; color: $gray-900; }
}
}
.pp-body { padding: 20px 24px; }
.pp-options { display: flex; flex-direction: column; gap: 12px; }
.pp-option {
border: 1px solid $border; border-radius: 8px; padding: 16px 20px;
transition: all 0.15s;
&:hover { border-color: #cbd5e1; background: $gray-50; }
&--selected { border-color: $primary; border-width: 2px; background: rgba($primary, 0.02); }
&__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
&__title { font-size: 14px; font-weight: 600; color: $gray-900; }
&__desc { font-size: 13px; color: $gray-700; line-height: 1.5; }
}
.pp-actions {
display: flex; justify-content: flex-end; gap: 10px; padding: 14px 24px;
border-top: 1px solid $gray-200; background: $gray-50;
&__cancel { border-radius: 8px !important; font-size: 13px !important; font-weight: 600 !important; height: 36px !important; border-color: $border !important; color: $gray-700 !important; }
&__submit { border-radius: 8px !important; font-size: 13px !important; font-weight: 600 !important; height: 36px !important; box-shadow: 0 1px 3px rgba($primary, 0.3) !important; i { margin-right: 6px; font-size: 12px; } }
}
@@ -6,12 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="cp-container">
<div class="cp-header">
<button mat-stroked-button [routerLink]="['../']" class="cp-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Payment Type' | translate }}
</button>
<h2 class="cp-header__title">{{ 'labels.heading.Create Payment Type' | translate }}</h2>
</div>
<div class="cp-card">
<form [formGroup]="paymentTypeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<div class="cp-section">
<div class="cp-section__header">
<i class="fas fa-dollar-sign"></i>
<h3>{{ 'labels.heading.Payment Type Details' | translate }}</h3>
</div>
<div class="cp-grid">
<mat-form-field appearance="outline" class="cp-field">
<mat-label>{{ 'labels.inputs.Payment Type' | translate }}</mat-label>
<input required matInput formControlName="name" />
@if (paymentTypeForm.controls.name.hasError('required')) {
@@ -22,16 +33,7 @@
}
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="isCashPayment" class="status">
{{ 'labels.inputs.Is Cash Payment' | translate }}?
</mat-checkbox>
<mat-form-field>
<mat-form-field appearance="outline" class="cp-field">
<mat-label>{{ 'labels.inputs.Position' | translate }}</mat-label>
<input required matInput type="number" min="1" formControlName="position" />
@if (paymentTypeForm.controls.position.hasError('required')) {
@@ -44,22 +46,35 @@
<mat-error> {{ 'labels.inputs.Position' | translate }} must be a positive number. </mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<mat-form-field appearance="outline" class="cp-field cp-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
</mat-form-field>
<div class="cp-checkbox-row">
<mat-checkbox labelPosition="after" formControlName="isCashPayment">
{{ 'labels.inputs.Is Cash Payment' | translate }}?
</mat-checkbox>
</div>
</div>
</div>
<div class="cp-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="cp-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!paymentTypeForm.valid"
class="cp-actions__submit"
*mifosxHasPermission="'CREATE_PAYMENTTYPE'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,10 +6,133 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
}
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.status {
padding: 17.5px 0;
:host {
display: block;
.cp-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.cp-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.cp-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.cp-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.cp-section {
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.cp-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.cp-field {
width: 100% !important;
&--full {
grid-column: 1 / -1;
}
}
.cp-checkbox-row {
grid-column: 1 / -1;
padding: 4px 0;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
}
}
.cp-actions {
display: flex !important;
justify-content: flex-end !important;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50 !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
}
@@ -6,12 +6,23 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="ep-container">
<div class="ep-header">
<button mat-stroked-button [routerLink]="['../../']" class="ep-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Payment Type' | translate }}
</button>
<h2 class="ep-header__title">{{ 'labels.heading.Edit Payment Type' | translate }}</h2>
</div>
<div class="ep-card">
<form [formGroup]="paymentTypeForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<div class="ep-section">
<div class="ep-section__header">
<i class="fas fa-dollar-sign"></i>
<h3>{{ 'labels.heading.Payment Type Details' | translate }}</h3>
</div>
<div class="ep-grid">
<mat-form-field appearance="outline" class="ep-field">
<mat-label>{{ 'labels.inputs.Payment Type' | translate }}</mat-label>
<input required matInput formControlName="name" />
@if (paymentTypeForm.controls.name.hasError('required')) {
@@ -22,44 +33,48 @@
}
</mat-form-field>
<mat-form-field>
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
</mat-form-field>
<mat-checkbox labelPosition="before" formControlName="isCashPayment" class="status">
{{ 'labels.inputs.Is Cash Payment' | translate }}?
</mat-checkbox>
<mat-form-field>
<mat-form-field appearance="outline" class="ep-field">
<mat-label>{{ 'labels.inputs.Position' | translate }}</mat-label>
<input required matInput type="number" min="1" formControlName="position" />
@if (paymentTypeForm.controls.position.hasError('min')) {
<mat-error> {{ 'labels.inputs.Position' | translate }} must be a positive number. </mat-error>
}
@if (paymentTypeForm.controls.position.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Position' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
@if (paymentTypeForm.controls.position.hasError('min')) {
<mat-error> {{ 'labels.inputs.Position' | translate }} must be a positive number. </mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../../']">
<mat-form-field appearance="outline" class="ep-field ep-field--full">
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
<textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
</mat-form-field>
<div class="ep-checkbox-row">
<mat-checkbox labelPosition="after" formControlName="isCashPayment">
{{ 'labels.inputs.Is Cash Payment' | translate }}?
</mat-checkbox>
</div>
</div>
</div>
<div class="ep-actions">
<button type="button" mat-stroked-button [routerLink]="['../../']" class="ep-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!paymentTypeForm.valid"
class="ep-actions__submit"
*mifosxHasPermission="'UPDATE_PAYMENTTYPE'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,10 +6,133 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
}
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.status {
padding: 17.5px 0;
:host {
display: block;
.ep-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.ep-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ep-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.ep-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.ep-section {
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.ep-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.ep-field {
width: 100% !important;
&--full {
grid-column: 1 / -1;
}
}
.ep-checkbox-row {
grid-column: 1 / -1;
padding: 4px 0;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
}
}
.ep-actions {
display: flex !important;
justify-content: flex-end !important;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50 !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
}
@@ -6,23 +6,31 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_PAYMENTTYPE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Payment Type' | translate }}
</button>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-dollar-sign title-icon"></i>
{{ 'labels.heading.Payment Type' | translate }}
</h2>
</div>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_PAYMENTTYPE'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create Payment Type' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8 table-container">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let paymentType">{{ paymentType.name }}</td>
@@ -41,48 +49,18 @@
<ng-container matColumnDef="isSystemDefined">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.System Defined' | translate }}</th>
<td mat-cell *matCellDef="let paymentType">
<div [className]="paymentType.isSystemDefined === true ? 'true text-center' : 'false text-center'">
@if (paymentType.isSystemDefined) {
<fa-icon
icon="check-circle"
size="lg"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
@if (!paymentType.isSystemDefined) {
<fa-icon
icon="times-circle"
size="lg"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
</div>
<span [class]="paymentType.isSystemDefined ? 'bool-badge bool-badge--yes' : 'bool-badge bool-badge--no'">
{{ paymentType.isSystemDefined ? ('tooltips.Yes' | translate) : ('tooltips.No' | translate) }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="isCashPayment">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Cash Payment' | translate }}</th>
<td mat-cell *matCellDef="let paymentType">
<div [className]="paymentType.isCashPayment === true ? 'true text-center' : 'false text-center'">
@if (paymentType.isCashPayment) {
<fa-icon
icon="check-circle"
size="lg"
matTooltip="{{ 'tooltips.Yes' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
@if (!paymentType.isCashPayment) {
<fa-icon
icon="times-circle"
size="lg"
matTooltip="{{ 'tooltips.No' | translate }}"
matTooltipPosition="right"
></fa-icon>
}
</div>
<span [class]="paymentType.isCashPayment ? 'bool-badge bool-badge--yes' : 'bool-badge bool-badge--no'">
{{ paymentType.isCashPayment ? ('tooltips.Yes' | translate) : ('tooltips.No' | translate) }}
</span>
</td>
</ng-container>
@@ -93,22 +71,22 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
<td mat-cell *matCellDef="let paymentType" class="layout-row">
<td mat-cell *matCellDef="let paymentType" (click)="$event.stopPropagation()">
@if (!paymentType.isSystemDefined) {
<button mat-button [routerLink]="[paymentType.id, 'edit']" color="primary" class="no-wrap">
<fa-icon icon="edit" class="m-r-10"></fa-icon>{{ 'labels.buttons.Edit' | translate }}
</button>
}
@if (!paymentType.isSystemDefined) {
<button mat-button (click)="deletePaymentType(paymentType.id)" color="warn" class="no-wrap">
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
</button>
<div class="action-links">
<a class="cell-link" [routerLink]="[paymentType.id, 'edit']">
<i class="fas fa-pen" style="margin-right: 4px; font-size: 11px;"></i>{{ 'labels.buttons.Edit' | translate }}
</a>
<a class="cell-link cell-link--danger" (click)="deletePaymentType(paymentType.id)">
<i class="fas fa-trash-alt" style="margin-right: 4px; font-size: 11px;"></i>{{ 'labels.buttons.Delete' | translate }}
</a>
</div>
}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
@@ -6,19 +6,53 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.table-container {
border-radius: 10px;
overflow: hidden;
@use '../../shared/styles/list-layout' as *;
$primary: #1353a0;
.bool-badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 12px;
letter-spacing: 0.02em;
white-space: nowrap;
&::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
&--yes {
background: #ecfdf5;
color: #059669;
&::before { background: #059669; }
}
&--no {
background: #f1f5f9;
color: #94a3b8;
&::before { background: #94a3b8; }
}
}
table {
width: 100%;
.action-links {
display: flex;
gap: 12px;
align-items: center;
}
.true {
color: #32cd32;
}
.cell-link--danger {
color: #dc2626 !important;
cursor: pointer;
.false {
color: #f44366;
&:hover { text-decoration: underline; }
}
@@ -6,23 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button mat-raised-button color="primary" [routerLink]="['create']" *mifosxHasPermission="'CREATE_SMSCAMPAIGN'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Create SMS Campaign' | translate }}
</button>
</div>
<mat-card class="container">
<!-- Header Bar -->
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-envelope"></i>
{{ 'labels.heading.SMS Campaigns' | translate }}
</h2>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<input matInput placeholder="{{ 'labels.inputs.Filter' | translate }}" (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" [routerLink]="['create']" class="btn-create" *mifosxHasPermission="'CREATE_SMSCAMPAIGN'">
<fa-icon icon="plus"></fa-icon>
{{ 'labels.buttons.Create SMS Campaign' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8 table-container">
<table mat-table [dataSource]="dataSource" matSort>
<!-- Table -->
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="campaignName">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.name' | translate }}</th>
<td mat-cell *matCellDef="let smsCampaign">{{ smsCampaign.campaignName }}</td>
@@ -63,9 +74,9 @@
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="select-row" [routerLink]="[row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
</div>
</mat-card>
@@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
@use '../../shared/styles/list-layout' as *;
table {
width: 100%;
@@ -6,89 +6,88 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container input">
<div class="si-container">
@if (!isCollapsed) {
<mat-card>
<div class="si-header">
<button mat-stroked-button [routerLink]="['../']" class="si-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
</button>
<h2 class="si-header__title">{{ 'labels.heading.Standing Instructions History' | translate }}</h2>
</div>
<div class="si-card">
<form [formGroup]="instructionForm" (ngSubmit)="search()">
<mat-card-content>
<div class="layout-column">
<mat-form-field>
<div class="si-section">
<div class="si-section__header">
<i class="fas fa-search"></i>
<h3>Search Parameters</h3>
</div>
<div class="si-grid">
<mat-form-field appearance="outline" class="si-field">
<mat-label>{{ 'labels.inputs.Client Name' | translate }}</mat-label>
<input matInput formControlName="clientName" />
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="si-field">
<mat-label>{{ 'labels.inputs.Client Id' | translate }}</mat-label>
<input matInput formControlName="clientId" />
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="si-field">
<mat-label>{{ 'labels.inputs.Transfer Type' | translate }}</mat-label>
<mat-select formControlName="transferType">
@for (transferType of standingInstructionsTemplate.transferTypeOptions; track transferType) {
<mat-option [value]="transferType.id">
{{ transferType.value }}
</mat-option>
<mat-option [value]="transferType.id">{{ transferType.value }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-form-field appearance="outline" class="si-field">
<mat-label>{{ 'labels.inputs.Account Type' | translate }}</mat-label>
<mat-select formControlName="fromAccountType">
@for (accountType of standingInstructionsTemplate.fromAccountTypeOptions; track accountType) {
<mat-option [value]="accountType.id">
{{ accountType.value }}
</mat-option>
<mat-option [value]="accountType.id">{{ accountType.value }}</mat-option>
}
</mat-select>
</mat-form-field>
@if (instructionForm.contains('fromAccountId')) {
<mat-form-field>
<mat-form-field appearance="outline" class="si-field">
<mat-label>{{ 'labels.inputs.From Account ID' | translate }}</mat-label>
<input matInput formControlName="fromAccountId" />
</mat-form-field>
}
<mat-form-field (click)="fromDatePicker.open()">
<mat-form-field appearance="outline" class="si-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
formControlName="fromDate"
[matDatepicker]="fromDatePicker"
/>
<input matInput [min]="minDate" [max]="maxDate" formControlName="fromDate" [matDatepicker]="fromDatePicker" />
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
<mat-datepicker #fromDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field (click)="toDatePicker.open()">
<mat-form-field appearance="outline" class="si-field" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To Date' | translate }}</mat-label>
<input matInput [min]="minDate" [max]="maxDate" formControlName="toDate" [matDatepicker]="toDatePicker" />
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
<mat-datepicker #toDatePicker></mat-datepicker>
</mat-form-field>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button mat-raised-button color="primary" *mifosxHasPermission="'READ_STANDINGINSTRUCTION'">
<fa-icon icon="search"></fa-icon>
&nbsp; {{ 'labels.buttons.Search Instructions' | translate }}
</button>
</mat-card-actions>
</form>
</mat-card>
}
</div>
</div>
@if (isCollapsed) {
<div class="container output">
<mat-card>
<div class="m-b-20">
<button mat-raised-button color="primary" (click)="isCollapsed = false">
{{ 'labels.buttons.Parameters' | translate }}
</button>
</div>
<table class="mat-elevation-z1" mat-table [dataSource]="dataSource" matSort>
<div class="si-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="si-actions__cancel">{{ 'labels.buttons.Cancel' | translate }}</button>
<button type="submit" mat-flat-button color="primary" class="si-actions__submit" *mifosxHasPermission="'READ_STANDINGINSTRUCTION'">
<i class="fas fa-search"></i> {{ 'labels.buttons.Search Instructions' | translate }}
</button>
</div>
</form>
</div>
}
@if (isCollapsed) {
<div class="si-header">
<button mat-stroked-button (click)="isCollapsed = false" class="si-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.buttons.Parameters' | translate }}
</button>
<h2 class="si-header__title">{{ 'labels.heading.Standing Instructions History' | translate }}</h2>
</div>
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="fromClient">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.From Client' | translate }}</th>
<td mat-cell *matCellDef="let instruction">
@@ -139,6 +138,6 @@
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</mat-card>
</div>
}
</div>
}
</div>
@@ -6,11 +6,182 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.input {
max-width: 37rem;
@use '../../shared/styles/list-layout' as *;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.si-container {
max-width: 860px;
margin: 0 auto;
padding: 28px 24px;
}
.output {
.si-header {
margin-bottom: 24px;
&__title {
font-size: 22px;
font-weight: 600;
color: $gray-900;
margin: 16px 0 0;
}
}
.si-back {
border: 1px solid $border !important;
color: $gray-700 !important;
font-size: 13px;
font-weight: 500;
border-radius: 8px !important;
padding: 4px 14px !important;
transition: all 0.2s ease;
i {
margin-right: 6px;
font-size: 12px;
}
&:hover {
background: $gray-50 !important;
border-color: $gray-500 !important;
}
}
.si-card {
background: #fff;
border: 1px solid $border;
border-radius: 12px;
padding: 0;
box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04);
}
.si-section {
padding: 24px 28px;
border-bottom: 1px solid $border;
&__header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
i {
font-size: 16px;
color: $primary;
}
h3 {
font-size: 15px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
}
.si-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px 20px;
@media (max-width: 600px) {
grid-template-columns: 1fr;
}
}
.si-field {
width: 100%;
}
.si-actions {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
padding: 18px 28px;
&__cancel {
border: 1px solid $border !important;
color: $gray-700 !important;
border-radius: 8px !important;
font-weight: 500;
font-size: 13px;
padding: 4px 20px !important;
transition: all 0.2s ease;
&:hover {
background: $gray-50 !important;
border-color: $gray-500 !important;
}
}
&__submit {
background: $primary !important;
color: #fff !important;
border-radius: 8px !important;
font-weight: 500;
font-size: 13px;
padding: 4px 20px !important;
box-shadow: 0 1px 2px rgba(19, 83, 160, 0.15);
transition: all 0.2s ease;
i {
margin-right: 6px;
font-size: 12px;
}
&:hover {
background: #0f4688 !important;
box-shadow: 0 2px 6px rgba(19, 83, 160, 0.25);
}
}
}
// Results table
.table-container {
background: #fff;
border: 1px solid $border;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04);
}
.modern-table {
width: 100%;
th.mat-header-cell {
background: $gray-50;
color: $gray-700;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.4px;
padding: 14px 16px;
border-bottom: 1px solid $border;
}
td.mat-cell {
color: $gray-900;
font-size: 13px;
padding: 12px 16px;
border-bottom: 1px solid $border;
}
tr.mat-row:hover {
background: $gray-50;
}
tr.mat-row:last-child td.mat-cell {
border-bottom: none;
}
.error-log {
min-width: 26px;
padding: 0 6px;
@@ -18,3 +189,8 @@
line-height: 25px;
}
}
::ng-deep .mat-mdc-paginator {
border-top: 1px solid $border;
background: $gray-50;
}
@@ -6,32 +6,51 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="ac-container">
<div class="ac-header">
<button mat-stroked-button [routerLink]="['../']" class="ac-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Cashiers' | translate }}
</button>
<h2 class="ac-header__title">{{ 'labels.heading.Allocate Cash' | translate }}</h2>
</div>
<div class="ac-card">
<form [formGroup]="allocateCashForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<div class="ac-section">
<div class="ac-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Cashier Details' | translate }}</h3>
</div>
<div class="ac-grid">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<input matInput required formControlName="office" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Teller Name' | translate }}</mat-label>
<input matInput required formControlName="tellerName" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Cashier' | translate }}</mat-label>
<input matInput required formControlName="cashier" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Assignment Period' | translate }}</mat-label>
<input matInput formControlName="assignmentPeriod" />
</mat-form-field>
</div>
</div>
<mat-form-field class="flex-48" (click)="cashDatePicker.open()">
<div class="ac-section">
<div class="ac-section__header">
<i class="fas fa-arrow-up"></i>
<h3>{{ 'labels.heading.Allocate Cash' | translate }}</h3>
</div>
<div class="ac-grid">
<mat-form-field appearance="outline" class="ac-field" (click)="cashDatePicker.open()">
<mat-label>{{ 'labels.inputs.Date' | translate }}</mat-label>
<input
matInput
@@ -51,7 +70,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currencyCode">
@for (currency of cashierData.currencyOptions; track currency) {
@@ -68,7 +87,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input matInput required formControlName="txnAmount" />
@if (allocateCashForm.controls.txnAmount.hasError('required')) {
@@ -79,7 +98,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ac-field">
<mat-label>{{ 'labels.inputs.Notes/Comments' | translate }}</mat-label>
<input matInput required formControlName="txnNote" />
@if (allocateCashForm.controls.txnNote.hasError('required')) {
@@ -90,21 +109,23 @@
}
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="ac-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ac-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!allocateCashForm.valid"
class="ac-actions__submit"
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -5,3 +5,125 @@
* 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/.
*/
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
:host {
display: block;
.ac-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.ac-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ac-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.ac-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.ac-section {
& + & {
border-top: 1px solid $gray-200;
}
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.ac-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.ac-field {
width: 100% !important;
}
.ac-actions {
display: flex !important;
justify-content: flex-end !important;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50 !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
}
@@ -6,28 +6,31 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container m-b-20 layout-row align-end gap-20px">
<button
mat-raised-button
color="primary"
[routerLink]="['create']"
*mifosxHasPermission="'CREATE_CASHIERS_FOR_TELLER'"
>
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.New Cashier' | translate }}
</button>
</div>
<div class="container">
<div class="layout-row gap-20px">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-user-tie title-icon"></i>
{{ 'labels.heading.Cashiers' | translate }}
</h2>
</div>
<div class="table-actions">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<div class="table-buttons">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['create']" *mifosxHasPermission="'CREATE_CASHIERS_FOR_TELLER'">
<fa-icon icon="plus" class="m-r-10"></fa-icon>
{{ 'labels.buttons.New Cashier' | translate }}
</button>
</div>
</div>
</div>
<div class="mat-elevation-z8">
<table mat-table [dataSource]="dataSource" matSort>
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="period">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Period' | translate }}</th>
<td mat-cell *matCellDef="let cashier">
@@ -46,46 +49,39 @@
</ng-container>
<ng-container matColumnDef="vaultActions">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Vault Actions' | translate }}</th>
<td mat-cell *matCellDef="let cashier">
<button
class="cashier-action-button"
mat-raised-button
color="accent"
matTooltip="{{ 'tooltips.Allocate Cash' | translate }}"
(click)="routeEdit($event)"
[routerLink]="[cashier.id, 'allocate']"
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
>
<i class="fa fa-arrow-up"></i>
</button>
<button
class="cashier-action-button"
mat-raised-button
color="warn"
matTooltip="{{ 'tooltips.Settle Cash' | translate }}"
(click)="routeEdit($event)"
[routerLink]="[cashier.id, 'settle']"
*mifosxHasPermission="'SETTLECASHIER_TELLER'"
>
<i class="fa fa-arrow-down"></i>
</button>
<button
class="cashier-action-button"
mat-raised-button
color="primary"
matTooltip="{{ 'tooltips.View Transactions' | translate }}"
(click)="routeEdit($event)"
[routerLink]="[cashier.id, 'transactions']"
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
>
<i class="fa fa-eye"></i>
</button>
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Vault Actions' | translate }}</th>
<td mat-cell *matCellDef="let cashier" (click)="$event.stopPropagation()">
<div class="vault-actions">
<a
class="vault-btn vault-btn--allocate"
matTooltip="{{ 'tooltips.Allocate Cash' | translate }}"
[routerLink]="[cashier.id, 'allocate']"
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
>
<i class="fas fa-arrow-up"></i>
</a>
<a
class="vault-btn vault-btn--settle"
matTooltip="{{ 'tooltips.Settle Cash' | translate }}"
[routerLink]="[cashier.id, 'settle']"
*mifosxHasPermission="'SETTLECASHIER_TELLER'"
>
<i class="fas fa-arrow-down"></i>
</a>
<a
class="vault-btn vault-btn--view"
matTooltip="{{ 'tooltips.View Transactions' | translate }}"
[routerLink]="[cashier.id, 'transactions']"
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
>
<i class="fas fa-eye"></i>
</a>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="select-row" [routerLink]="[row.id]"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row" [routerLink]="[row.id]"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
@@ -6,17 +6,47 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
table {
width: 100%;
@use '../../../shared/styles/list-layout' as *;
.cashier-action-button {
min-width: 26px;
padding: 0 6px;
margin: 4px;
line-height: 25px;
$primary: #1353a0;
.vault-actions {
display: flex;
gap: 6px;
}
.vault-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: opacity 0.15s, transform 0.15s;
text-decoration: none;
&:hover {
opacity: 0.85;
transform: translateY(-1px);
}
.select-row:hover {
cursor: pointer;
&--allocate {
background: #ecfdf5;
color: #059669;
border: 1px solid #a7f3d0;
}
&--settle {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
&--view {
background: #eff6ff;
color: $primary;
border: 1px solid #bfdbfe;
}
}
@@ -6,22 +6,33 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="cc-container">
<div class="cc-header">
<button mat-stroked-button [routerLink]="['../']" class="cc-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Cashiers' | translate }}
</button>
<h2 class="cc-header__title">{{ 'labels.heading.Create Cashier' | translate }}</h2>
</div>
<div class="cc-card">
<form [formGroup]="createCashierForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<div class="cc-section">
<div class="cc-section__header">
<i class="fas fa-user-tie"></i>
<h3>{{ 'labels.heading.Cashier Assignment' | translate }}</h3>
</div>
<div class="cc-grid">
<mat-form-field appearance="outline" class="cc-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<input matInput required autofocus [value]="cashierTemplate.officeName" disabled />
<input matInput required [value]="cashierTemplate.officeName" disabled />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="cc-field">
<mat-label>{{ 'labels.inputs.Teller Name' | translate }}</mat-label>
<input matInput required autofocus [value]="cashierTemplate.tellerName" disabled />
<input matInput required [value]="cashierTemplate.tellerName" disabled />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="cc-field">
<mat-label>{{ 'labels.inputs.Cashier' | translate }}</mat-label>
<mat-select formControlName="staffId">
@for (staff of cashierTemplate.staffOptions; track staff) {
@@ -38,12 +49,20 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="cc-field">
<mat-label>{{ 'labels.inputs.Description/Notes' | translate }}</mat-label>
<input matInput autofocus formControlName="description" />
<input matInput formControlName="description" />
</mat-form-field>
</div>
</div>
<mat-form-field class="flex-48" (click)="fromDatePicker.open()">
<div class="cc-section">
<div class="cc-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.heading.Schedule' | translate }}</h3>
</div>
<div class="cc-grid">
<mat-form-field appearance="outline" class="cc-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From' | translate }}</mat-label>
<input
matInput
@@ -63,7 +82,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48" (click)="toDatePicker.open()">
<mat-form-field appearance="outline" class="cc-field" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To' | translate }}</mat-label>
<input
matInput
@@ -84,21 +103,17 @@
</mat-form-field>
@if (!createCashierForm.value.isFullDay) {
<div class="flex-48 layout-row gap-10px">
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
>
<div class="cc-time-row">
<mat-form-field appearance="outline" class="cc-time-field">
<mat-label>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label>
<mat-select formControlName="hourStartTime">
@for (hour of hours; track hour) {
<mat-option [value]="hour">{{ hour }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
>
<mat-form-field appearance="outline" class="cc-time-field">
<mat-label>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label>
<mat-select formControlName="minStartTime">
@for (minute of minutes; track minute) {
<mat-option [value]="minute">{{ minute }}</mat-option>
@@ -106,21 +121,17 @@
</mat-select>
</mat-form-field>
</div>
<div class="flex-48 layout-row gap-10px">
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
>
<div class="cc-time-row">
<mat-form-field appearance="outline" class="cc-time-field">
<mat-label>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label>
<mat-select formControlName="hourEndTime">
@for (hour of hours; track hour) {
<mat-option [value]="hour">{{ hour }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
>
<mat-form-field appearance="outline" class="cc-time-field">
<mat-label>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label>
<mat-select formControlName="minEndTime">
@for (minute of minutes; track minute) {
<mat-option [value]="minute">{{ minute }}</mat-option>
@@ -129,7 +140,7 @@
</mat-form-field>
</div>
@if (createCashierForm.hasError('invalidTimeRange')) {
<div class="flex-48">
<div class="cc-field--full">
<mat-error>
{{ 'labels.inputs.End Time' | translate }} {{ 'labels.commons.must' | translate }}
{{ 'labels.commons.be' | translate }} {{ 'labels.commons.greater than' | translate }}
@@ -139,27 +150,29 @@
}
}
<div class="space flex-100">
<mat-checkbox labelPosition="before" formControlName="isFullDay">
<div class="cc-checkbox-row">
<mat-checkbox labelPosition="after" formControlName="isFullDay">
{{ 'labels.inputs.Full Day' | translate }}?
</mat-checkbox>
</div>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="cc-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="cc-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!createCashierForm.valid"
class="cc-actions__submit"
*mifosxHasPermission="'UPDATECASHIERALLOCATION_TELLER'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,149 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.space {
padding-top: 10px;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
:host {
display: block;
.cc-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.cc-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.cc-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.cc-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.cc-section {
& + & {
border-top: 1px solid $gray-200;
}
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.cc-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.cc-field {
width: 100% !important;
&--full {
grid-column: 1 / -1;
}
}
.cc-time-row {
display: flex !important;
gap: 12px;
@media (max-width: 600px) { flex-direction: column; gap: 4px; }
}
.cc-time-field {
flex: 1;
width: 100% !important;
}
.cc-checkbox-row {
grid-column: 1 / -1;
padding: 4px 0;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
}
}
.cc-actions {
display: flex !important;
justify-content: flex-end !important;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50 !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
}
@@ -6,23 +6,34 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="ec-container">
<div class="ec-header">
<button mat-stroked-button [routerLink]="['../']" class="ec-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Cashiers' | translate }}
</button>
<h2 class="ec-header__title">{{ 'labels.heading.Edit Cashier' | translate }}</h2>
</div>
<div class="ec-card">
<form [formGroup]="editCashierForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<div class="ec-section">
<div class="ec-section__header">
<i class="fas fa-user-tie"></i>
<h3>{{ 'labels.heading.Cashier Assignment' | translate }}</h3>
</div>
<div class="ec-grid">
<mat-form-field appearance="outline" class="ec-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<input matInput required autofocus [value]="cashierData.template.officeName" disabled />
<input matInput required [value]="cashierData.template.officeName" disabled />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ec-field">
<mat-label>{{ 'labels.inputs.Teller Name' | translate }}</mat-label>
<input matInput required autofocus [value]="cashierData.data.tellerName" disabled />
<input matInput required [value]="cashierData.data.tellerName" disabled />
</mat-form-field>
@if (isStaffId) {
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ec-field">
<mat-label>{{ 'labels.inputs.Cashier' | translate }}</mat-label>
<mat-select formControlName="staffId">
@for (staff of cashierData.template.staffOptions; track staff) {
@@ -35,24 +46,30 @@
}
@if (!isStaffId) {
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ec-field">
<mat-label>{{ 'labels.inputs.Cashier' | translate }}</mat-label>
@if (!isStaffId) {
<mat-select formControlName="staffId">
<mat-option [value]="cashierData.data.staffId">
{{ 'labels.inputs.Select Option' | translate }}
</mat-option>
</mat-select>
}
<mat-select formControlName="staffId">
<mat-option [value]="cashierData.data.staffId">
{{ 'labels.inputs.Select Option' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
}
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="ec-field">
<mat-label>{{ 'labels.inputs.Description/Notes' | translate }}</mat-label>
<input matInput autofocus formControlName="description" />
<input matInput formControlName="description" />
</mat-form-field>
</div>
</div>
<mat-form-field class="flex-48" (click)="fromDatePicker.open()">
<div class="ec-section">
<div class="ec-section__header">
<i class="fas fa-calendar-alt"></i>
<h3>{{ 'labels.heading.Schedule' | translate }}</h3>
</div>
<div class="ec-grid">
<mat-form-field appearance="outline" class="ec-field" (click)="fromDatePicker.open()">
<mat-label>{{ 'labels.inputs.From' | translate }}</mat-label>
<input
matInput
@@ -72,7 +89,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48" (click)="toDatePicker.open()">
<mat-form-field appearance="outline" class="ec-field" (click)="toDatePicker.open()">
<mat-label>{{ 'labels.inputs.To' | translate }}</mat-label>
<input
matInput
@@ -93,21 +110,17 @@
</mat-form-field>
@if (!editCashierForm.value.isFullDay) {
<div class="flex-48 layout-row gap-10px">
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
>
<div class="ec-time-row">
<mat-form-field appearance="outline" class="ec-time-field">
<mat-label>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label>
<mat-select formControlName="hourStartTime">
@for (hour of hours; track hour) {
<mat-option [value]="hour">{{ hour }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
>
<mat-form-field appearance="outline" class="ec-time-field">
<mat-label>{{ 'labels.inputs.Start Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label>
<mat-select formControlName="minStartTime">
@for (minute of minutes; track minute) {
<mat-option [value]="minute">{{ minute }}</mat-option>
@@ -115,21 +128,17 @@
</mat-select>
</mat-form-field>
</div>
<div class="flex-48 layout-row gap-10px">
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label
>
<div class="ec-time-row">
<mat-form-field appearance="outline" class="ec-time-field">
<mat-label>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Hour' | translate }})</mat-label>
<mat-select formControlName="hourEndTime">
@for (hour of hours; track hour) {
<mat-option [value]="hour">{{ hour }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field class="flex-50">
<mat-label
>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label
>
<mat-form-field appearance="outline" class="ec-time-field">
<mat-label>{{ 'labels.inputs.End Time' | translate }} ({{ 'labels.inputs.Minute' | translate }})</mat-label>
<mat-select formControlName="minEndTime">
@for (minute of minutes; track minute) {
<mat-option [value]="minute">{{ minute }}</mat-option>
@@ -138,7 +147,7 @@
</mat-form-field>
</div>
@if (editCashierForm.hasError('invalidTimeRange')) {
<div class="flex-48">
<div class="ec-field--full">
<mat-error>
{{ 'labels.inputs.End Time' | translate }} {{ 'labels.commons.must' | translate }}
{{ 'labels.commons.be' | translate }} {{ 'labels.commons.greater than' | translate }}
@@ -148,27 +157,29 @@
}
}
<div class="space flex-100">
<mat-checkbox labelPosition="before" formControlName="isFullDay">
<div class="ec-checkbox-row">
<mat-checkbox labelPosition="after" formControlName="isFullDay">
{{ 'labels.inputs.Full Day' | translate }}?
</mat-checkbox>
</div>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="ec-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="ec-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!editCashierForm.valid"
class="ec-actions__submit"
*mifosxHasPermission="'UPDATECASHIERALLOCATION_TELLER'"
>
{{ 'labels.buttons.Submit' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Submit' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -6,6 +6,149 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.space {
padding-top: 10px;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
:host {
display: block;
.ec-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.ec-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.ec-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.ec-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.ec-section {
& + & {
border-top: 1px solid $gray-200;
}
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.ec-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.ec-field {
width: 100% !important;
&--full {
grid-column: 1 / -1;
}
}
.ec-time-row {
display: flex !important;
gap: 12px;
@media (max-width: 600px) { flex-direction: column; gap: 4px; }
}
.ec-time-field {
flex: 1;
width: 100% !important;
}
.ec-checkbox-row {
grid-column: 1 / -1;
padding: 4px 0;
::ng-deep .mat-mdc-checkbox {
font-size: 13px;
}
}
.ec-actions {
display: flex !important;
justify-content: flex-end !important;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50 !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
}
@@ -6,32 +6,51 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="container">
<mat-card>
<div class="sc-container">
<div class="sc-header">
<button mat-stroked-button [routerLink]="['../']" class="sc-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Cashiers' | translate }}
</button>
<h2 class="sc-header__title">{{ 'labels.heading.Settle Cash' | translate }}</h2>
</div>
<div class="sc-card">
<form [formGroup]="settleCashForm" (ngSubmit)="submit()">
<mat-card-content>
<div class="layout-row-wrap gap-2px responsive-column">
<mat-form-field class="flex-48">
<div class="sc-section">
<div class="sc-section__header">
<i class="fas fa-info-circle"></i>
<h3>{{ 'labels.heading.Cashier Details' | translate }}</h3>
</div>
<div class="sc-grid">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
<input matInput required formControlName="office" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Teller Name' | translate }}</mat-label>
<input matInput required formControlName="tellerName" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Cashier' | translate }}</mat-label>
<input matInput required formControlName="cashier" />
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Assignment Period' | translate }}</mat-label>
<input matInput formControlName="assignmentPeriod" />
</mat-form-field>
</div>
</div>
<mat-form-field class="flex-48" (click)="cashDatePicker.open()">
<div class="sc-section">
<div class="sc-section__header">
<i class="fas fa-arrow-down"></i>
<h3>{{ 'labels.heading.Settle Cash' | translate }}</h3>
</div>
<div class="sc-grid">
<mat-form-field appearance="outline" class="sc-field" (click)="cashDatePicker.open()">
<mat-label>{{ 'labels.inputs.Date' | translate }}</mat-label>
<input
matInput
@@ -51,7 +70,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
<mat-select required formControlName="currencyCode">
@for (currency of cashierData.currencyOptions; track currency) {
@@ -68,7 +87,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Amount' | translate }}</mat-label>
<input matInput required formControlName="txnAmount" />
@if (settleCashForm.controls.txnAmount.hasError('required')) {
@@ -79,7 +98,7 @@
}
</mat-form-field>
<mat-form-field class="flex-48">
<mat-form-field appearance="outline" class="sc-field">
<mat-label>{{ 'labels.inputs.Notes/Comments' | translate }}</mat-label>
<input matInput required formControlName="txnNote" />
@if (settleCashForm.controls.txnNote.hasError('required')) {
@@ -90,21 +109,23 @@
}
</mat-form-field>
</div>
</mat-card-content>
</div>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" mat-raised-button [routerLink]="['../']">
<div class="sc-actions">
<button type="button" mat-stroked-button [routerLink]="['../']" class="sc-actions__cancel">
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-raised-button
type="submit"
mat-flat-button
color="primary"
[disabled]="!settleCashForm.valid"
class="sc-actions__submit"
*mifosxHasPermission="'SETTLECASHFROMCASHIER_TELLER'"
>
{{ 'labels.buttons.Settle Cash' | translate }}
<i class="fas fa-check"></i> {{ 'labels.buttons.Settle Cash' | translate }}
</button>
</mat-card-actions>
</div>
</form>
</mat-card>
</div>
</div>
@@ -5,3 +5,125 @@
* 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/.
*/
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
:host {
display: block;
.sc-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.sc-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
}
.sc-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.sc-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.sc-section {
& + & {
border-top: 1px solid $gray-200;
}
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.sc-grid {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 4px 16px;
padding: 16px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.sc-field {
width: 100% !important;
}
.sc-actions {
display: flex !important;
justify-content: flex-end !important;
gap: 10px;
padding: 14px 24px;
border-top: 1px solid $gray-200;
background: $gray-50 !important;
&__cancel {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
border-color: $border !important;
color: $gray-700 !important;
}
&__submit {
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 600 !important;
height: 36px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
}
}
@@ -6,128 +6,115 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row gap-1percent layout-align-end container m-b-20">
<button
mat-raised-button
color="primary"
[routerLink]="['../allocate']"
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
>
<fa-icon icon="arrow-up" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Allocate Cash' | translate }}
</button>
<button
mat-raised-button
color="warn"
[routerLink]="['../settle']"
*mifosxHasPermission="'SETTLECASHFROMCASHIER_TELLER'"
>
<fa-icon icon="arrow-down" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Settle Cash' | translate }}
</button>
</div>
@if (cashierData) {
<div class="container m-b-20">
<mat-card class="mat-elevation-z8">
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-25 header">
{{ 'labels.inputs.Branch' | translate }}
</div>
<div class="flex-25">
{{ cashierData.officeName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Teller' | translate }}
</div>
<div class="flex-25">
{{ cashierData.tellerName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Cashier' | translate }}
</div>
<div class="flex-25">
{{ cashierData.cashierName }}
</div>
<div class="flex-25 header">
{{ 'labels.inputs.Net Cash' | translate }}
</div>
<div class="flex-25">{{ cashierData.netCash | formatNumber }} [{{ currencySelector.value }}]</div>
</div>
</mat-card-content>
</mat-card>
</div>
}
<div class="container">
<mat-card class="layout-column gap-2percent mat-elevation-z8">
<div class="layout-row gap-2percent">
<mat-form-field class="flex-fill">
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<mat-form-field>
<mat-label> {{ 'labels.inputs.Select Currency' | translate }} </mat-label>
<mat-select [formControl]="currencySelector" required>
@for (currency of currencyData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
<div class="table-header">
<div class="table-title-group">
<h2 class="table-title">
<i class="fas fa-exchange-alt title-icon"></i>
{{ 'labels.heading.Cash Transactions' | translate }}
</h2>
</div>
@if (cashierData) {
<div>
<table class="mat-elevation-z1" mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="date">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Date' | translate }}</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.txnDate | dateFormat }}</td>
</ng-container>
<ng-container matColumnDef="transactions">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Transactions' | translate }}</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.txnType.value }}</td>
</ng-container>
<ng-container matColumnDef="allocation">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Allocation' | translate }} ({{ cashierData.sumCashAllocation | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Allocate Cash' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<ng-container matColumnDef="cashIn">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Cash In' | translate }} ({{ cashierData.sumInwardCash | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Cash In' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<ng-container matColumnDef="cashOut">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Cash Out' | translate }} ({{ cashierData.sumOutwardCash | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Cash Out' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<ng-container matColumnDef="settlement">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Settlement' | translate }} ({{ cashierData.sumCashSettlement | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Settle Cash' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
<div class="table-actions">
<div class="table-buttons">
<button mat-flat-button color="primary" class="btn-create" [routerLink]="['../allocate']" *mifosxHasPermission="'ALLOCATECASHIER_TELLER'">
<fa-icon icon="arrow-up" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Allocate Cash' | translate }}
</button>
<button mat-stroked-button class="btn-import" [routerLink]="['../settle']" *mifosxHasPermission="'SETTLECASHFROMCASHIER_TELLER'">
<fa-icon icon="arrow-down" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Settle Cash' | translate }}
</button>
</div>
}
</mat-card>
</div>
</div>
@if (cashierData) {
<div class="txn-summary">
<div class="txn-summary__item">
<span class="txn-summary__label">{{ 'labels.inputs.Branch' | translate }}</span>
<span class="txn-summary__value">{{ cashierData.officeName }}</span>
</div>
<div class="txn-summary__item">
<span class="txn-summary__label">{{ 'labels.inputs.Teller' | translate }}</span>
<span class="txn-summary__value">{{ cashierData.tellerName }}</span>
</div>
<div class="txn-summary__item">
<span class="txn-summary__label">{{ 'labels.inputs.Cashier' | translate }}</span>
<span class="txn-summary__value">{{ cashierData.cashierName }}</span>
</div>
<div class="txn-summary__item">
<span class="txn-summary__label">{{ 'labels.inputs.Net Cash' | translate }}</span>
<span class="txn-summary__value txn-summary__value--highlight">{{ cashierData.netCash | formatNumber }} [{{ currencySelector.value }}]</span>
</div>
</div>
}
<div class="table-actions" style="margin-bottom: 12px;">
<mat-form-field class="table-search" appearance="outline">
<i matPrefix class="fas fa-search"></i>
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
<input matInput (keyup)="applyFilter($event.target.value)" />
</mat-form-field>
<mat-form-field appearance="outline" class="table-search" style="max-width: 220px;">
<mat-label>{{ 'labels.inputs.Select Currency' | translate }}</mat-label>
<mat-select [formControl]="currencySelector" required>
@for (currency of currencyData; track currency) {
<mat-option [value]="currency.code">
{{ currency.name }}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
@if (cashierData) {
<div class="table-container">
<table mat-table [dataSource]="dataSource" matSort class="modern-table">
<ng-container matColumnDef="date">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Date' | translate }}</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.txnDate | dateFormat }}</td>
</ng-container>
<ng-container matColumnDef="transactions">
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'labels.inputs.Transactions' | translate }}</th>
<td mat-cell *matCellDef="let transaction">{{ transaction.txnType.value }}</td>
</ng-container>
<ng-container matColumnDef="allocation">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Allocation' | translate }} ({{ cashierData.sumCashAllocation | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Allocate Cash' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<ng-container matColumnDef="cashIn">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Cash In' | translate }} ({{ cashierData.sumInwardCash | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Cash In' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<ng-container matColumnDef="cashOut">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Cash Out' | translate }} ({{ cashierData.sumOutwardCash | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Cash Out' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<ng-container matColumnDef="settlement">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
{{ 'labels.inputs.Settlement' | translate }} ({{ cashierData.sumCashSettlement | formatNumber }})
</th>
<td mat-cell *matCellDef="let transaction">
{{ transaction.txnType.value === 'Settle Cash' ? (transaction.txnAmount | formatNumber) : '-' }}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
</div>
}
</div>
@@ -6,16 +6,53 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.content {
div {
line-height: 3rem;
@use '../../../../shared/styles/list-layout' as *;
&.header {
font-weight: 500;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.txn-summary {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 16px 20px;
background: #fff;
border: 1px solid $border;
border-radius: 10px;
margin-bottom: 16px;
@media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
&__item {
display: flex;
flex-direction: column;
gap: 4px;
}
&__label {
font-size: 11px;
font-weight: 600;
color: $gray-500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
&__value {
font-size: 14px;
font-weight: 500;
color: $gray-900;
&--highlight {
color: $primary;
font-weight: 600;
}
}
}
table {
width: 100%;
}
@@ -6,79 +6,55 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="layout-row align-end gap-2px responsive-column container m-b-20">
<button mat-raised-button color="primary" [routerLink]="['edit']" *mifosxHasPermission="'UPDATE_CASHIER'">
<fa-icon icon="edit" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Edit' | translate }}
</button>
<button mat-raised-button color="warn" (click)="deleteCashier()" *mifosxHasPermission="'DELETECASHIER_TELLER'">
<fa-icon icon="trash" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Delete' | translate }}
</button>
</div>
<div class="vc-container">
<div class="vc-header">
<button mat-stroked-button [routerLink]="['..']" class="vc-back">
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Cashiers' | translate }}
</button>
<h2 class="vc-header__title">{{ cashierData.staffName }}</h2>
<div class="vc-header__spacer"></div>
<button mat-flat-button color="primary" [routerLink]="['edit']" class="vc-header__action" *mifosxHasPermission="'UPDATE_CASHIER'">
<i class="fas fa-pen"></i> {{ 'labels.buttons.Edit' | translate }}
</button>
<button mat-stroked-button color="warn" (click)="deleteCashier()" class="vc-header__delete" *mifosxHasPermission="'DELETECASHIER_TELLER'">
<i class="fas fa-trash-alt"></i> {{ 'labels.buttons.Delete' | translate }}
</button>
</div>
<div class="container">
<mat-card>
<mat-card-content>
<div class="layout-row-wrap">
<div class="flex-50 header">
{{ 'labels.inputs.Name' | translate }}
<div class="vc-card">
<div class="vc-section">
<div class="vc-section__header">
<i class="fas fa-user-tie"></i>
<h3>{{ 'labels.heading.Cashier Details' | translate }}</h3>
</div>
<div class="vc-details">
<div class="vc-detail">
<span class="vc-detail__label">{{ 'labels.inputs.Name' | translate }}</span>
<span class="vc-detail__value">{{ cashierData.staffName }}</span>
</div>
<div class="flex-50">
{{ cashierData.staffName }}
<div class="vc-detail">
<span class="vc-detail__label">{{ 'labels.inputs.Teller' | translate }}</span>
<span class="vc-detail__value">{{ cashierData.tellerName }}</span>
</div>
<div class="flex-50 header">
{{ 'labels.inputs.Teller' | translate }}
</div>
<div class="flex-50">
{{ cashierData.tellerName }}
</div>
@if (cashierData.description !== '') {
<div class="flex-50 header">
{{ 'labels.inputs.Description' | translate }}
<div class="vc-detail vc-detail--full">
<span class="vc-detail__label">{{ 'labels.inputs.Description' | translate }}</span>
<span class="vc-detail__value">{{ cashierData.description }}</span>
</div>
}
@if (cashierData.description !== '') {
<div class="flex-50">
{{ cashierData.description }}
</div>
}
<div class="flex-50 header">
{{ 'labels.inputs.Start Date' | translate }}
<div class="vc-detail">
<span class="vc-detail__label">{{ 'labels.inputs.Start Date' | translate }}</span>
<span class="vc-detail__value">{{ cashierData.startDate | dateFormat }}</span>
</div>
<div class="flex-50">
{{ cashierData.startDate | dateFormat }}
<div class="vc-detail">
<span class="vc-detail__label">{{ 'labels.inputs.End Date' | translate }}</span>
<span class="vc-detail__value">{{ cashierData.endDate | dateFormat }}</span>
</div>
<div class="flex-50 header">
{{ 'labels.inputs.End Date' | translate }}
</div>
<div class="flex-50">
{{ cashierData.endDate | dateFormat }}
</div>
<div class="flex-50 header">
{{ 'labels.inputs.Full Day/Time' | translate }}
</div>
<div class="flex-50">
{{ cashierData.isFullDay }}
<div class="vc-detail">
<span class="vc-detail__label">{{ 'labels.inputs.Full Day/Time' | translate }}</span>
<span class="vc-detail__value">{{ cashierData.isFullDay | yesNo }}</span>
</div>
</div>
</mat-card-content>
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
<button type="button" color="primary" mat-raised-button [routerLink]="['..']">
{{ 'labels.buttons.Back' | translate }}
</button>
</mat-card-actions>
</mat-card>
</div>
</div>
</div>
@@ -6,17 +6,136 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
.container {
max-width: 37rem;
$primary: #1353a0;
$accent: #0ea5e9;
$border: #e2e8f0;
$gray-50: #f8f9fa;
$gray-200: #e8eaed;
$gray-500: #9aa0a6;
$gray-700: #5f6368;
$gray-900: #202124;
.content {
div {
margin: 1rem 0;
word-wrap: break-word;
:host {
display: block;
&.header {
font-weight: 500;
.vc-container {
max-width: 800px;
margin: 0 auto;
padding: 20px 16px;
}
.vc-header {
display: flex !important;
align-items: center !important;
gap: 14px;
margin-bottom: 20px;
flex-wrap: wrap;
&__title {
font-size: 18px;
font-weight: 600;
color: $gray-900;
margin: 0;
}
&__spacer {
flex: 1;
}
&__action {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
i { margin-right: 6px; font-size: 12px; }
}
&__delete {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
i { margin-right: 6px; font-size: 12px; }
}
}
.vc-back {
border-radius: 8px !important;
font-size: 12px !important;
font-weight: 600 !important;
height: 32px !important;
border-color: $border !important;
color: $gray-500 !important;
i { margin-right: 6px; }
&:hover { color: $primary !important; border-color: $primary !important; }
}
.vc-card {
background: #fff;
border: 1px solid $border;
border-radius: 10px;
overflow: hidden;
}
.vc-section {
&__header {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 10px;
padding: 12px 24px;
background: $gray-50 !important;
border-bottom: 1px solid $gray-200;
i {
font-size: 14px;
color: $accent;
}
h3 {
margin: 0 !important;
font-size: 14px !important;
font-weight: 600 !important;
color: $gray-900;
}
}
}
.vc-details {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: 16px;
padding: 20px 24px;
@media (max-width: 600px) { grid-template-columns: 1fr !important; }
}
.vc-detail {
display: flex !important;
flex-direction: column !important;
gap: 4px;
&--full {
grid-column: 1 / -1;
}
&__label {
font-size: 12px;
font-weight: 600;
color: $gray-500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
&__value {
font-size: 14px;
color: $gray-900;
word-wrap: break-word;
}
}
}

Some files were not shown because too many files have changed in this diff Show More