update accounting journal entry UI and API response
This commit is contained in:
+237
-143
@@ -7,159 +7,253 @@
|
||||
-->
|
||||
|
||||
@if (isJournalEntryLoaded) {
|
||||
<div>
|
||||
@if (dataSource?.data[0].manualEntry) {
|
||||
<div class="layout-row layout-align-end container m-b-20">
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
(click)="revertTransaction()"
|
||||
*mifosxHasPermission="'REVERSE_JOURNALENTRY'"
|
||||
[disabled]="dataSource?.data[0].reversed"
|
||||
>
|
||||
@if (!dataSource.data[0].reversed) {
|
||||
<span>
|
||||
<fa-icon icon="undo" class="m-r-10"></fa-icon>{{ 'labels.buttons.Revert Transaction' | translate }}
|
||||
</span>
|
||||
}
|
||||
@if (dataSource.data[0].reversed) {
|
||||
<span>{{ 'labels.buttons.Transaction Reverted' | translate }}</span>
|
||||
}
|
||||
<div class="txn-detail-page">
|
||||
<!-- Header Bar -->
|
||||
<div class="txn-header-bar">
|
||||
<div class="txn-header-bar__left">
|
||||
<button mat-icon-button (click)="goBack()" class="txn-back-btn" matTooltip="Go Back">
|
||||
<fa-icon icon="arrow-left"></fa-icon>
|
||||
</button>
|
||||
<div class="txn-header-bar__title-group">
|
||||
<h3 class="txn-header-bar__title">
|
||||
<fa-icon icon="receipt" class="m-r-8"></fa-icon>
|
||||
Transaction Detail
|
||||
</h3>
|
||||
@if (transactionId) {
|
||||
<span class="txn-header-bar__txn-id">ID: {{ transactionId }}</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (isViewTransaction()) {
|
||||
<div class="container m-b-20">
|
||||
@if (dataSource) {
|
||||
<mat-card class="mat-elevation-z8" ngClass="{{ journalEntryColor() }}">
|
||||
<mat-card-content>
|
||||
<div class="layout-row-wrap">
|
||||
<div class="flex-25 header">
|
||||
{{ 'labels.inputs.Office' | translate }}
|
||||
</div>
|
||||
<div class="flex-25">
|
||||
{{ dataSource.data[0].officeName }}
|
||||
</div>
|
||||
<div class="flex-25 header">
|
||||
{{ 'labels.inputs.Transaction Date' | translate }}
|
||||
</div>
|
||||
<div class="flex-25">
|
||||
{{ dataSource.data[0].transactionDate | dateFormat }}
|
||||
</div>
|
||||
<div class="flex-25 header">
|
||||
{{ 'labels.inputs.Created By' | translate }}
|
||||
</div>
|
||||
<div class="flex-25">
|
||||
{{ dataSource.data[0].createdByUserName }}
|
||||
</div>
|
||||
<div class="flex-25 header">
|
||||
{{ 'labels.inputs.Submitted on' | translate }}
|
||||
</div>
|
||||
<div class="flex-25">
|
||||
{{ dataSource.data[0].submittedOnDate | datetimeFormat }}
|
||||
</div>
|
||||
<div class="flex-25 header">
|
||||
{{ 'labels.inputs.Manual Journal Entry' | translate }}
|
||||
</div>
|
||||
<div class="flex-25">
|
||||
{{ dataSource.data[0].manualEntry | yesNo }}
|
||||
</div>
|
||||
@if (dataSource.data[0].externalAssetOwner) {
|
||||
<div class="flex-25 header">
|
||||
{{ 'labels.inputs.External Asset Owner' | translate }}
|
||||
</div>
|
||||
}
|
||||
@if (dataSource.data[0].externalAssetOwner) {
|
||||
<div class="flex-25">
|
||||
{{ dataSource.data[0].externalAssetOwner }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<div class="txn-header-bar__actions">
|
||||
@if (dataSource?.data[0].manualEntry) {
|
||||
<button
|
||||
mat-raised-button
|
||||
color="warn"
|
||||
(click)="revertTransaction()"
|
||||
*mifosxHasPermission="'REVERSE_JOURNALENTRY'"
|
||||
[disabled]="dataSource?.data[0].reversed"
|
||||
class="txn-revert-btn"
|
||||
>
|
||||
@if (!dataSource.data[0].reversed) {
|
||||
<span><fa-icon icon="undo" class="m-r-6"></fa-icon>Revert Transaction</span>
|
||||
} @else {
|
||||
<span><fa-icon icon="check-circle" class="m-r-6"></fa-icon>Reverted</span>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transaction Info Cards -->
|
||||
@if (isViewTransaction()) {
|
||||
@if (dataSource) {
|
||||
<div class="txn-info-grid">
|
||||
<div
|
||||
class="txn-info-card"
|
||||
[ngClass]="dataSource.data[0].manualEntry ? 'txn-info-card--manual' : 'txn-info-card--system'"
|
||||
>
|
||||
<div class="txn-info-card__icon">
|
||||
<fa-icon [icon]="dataSource.data[0].manualEntry ? 'pencil-alt' : 'cog'"></fa-icon>
|
||||
</div>
|
||||
<div class="txn-info-card__body">
|
||||
<div class="txn-info-card__label">Entry Type</div>
|
||||
<div class="txn-info-card__value">
|
||||
{{ dataSource.data[0].manualEntry ? 'Manual Entry' : 'System Entry' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="txn-info-card txn-info-card--blue">
|
||||
<div class="txn-info-card__icon"><fa-icon icon="building"></fa-icon></div>
|
||||
<div class="txn-info-card__body">
|
||||
<div class="txn-info-card__label">Office</div>
|
||||
<div class="txn-info-card__value">{{ dataSource.data[0].officeName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="txn-info-card txn-info-card--teal">
|
||||
<div class="txn-info-card__icon"><fa-icon icon="calendar-alt"></fa-icon></div>
|
||||
<div class="txn-info-card__body">
|
||||
<div class="txn-info-card__label">Transaction Date</div>
|
||||
<div class="txn-info-card__value">{{ dataSource.data[0].transactionDate | dateFormat }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="txn-info-card txn-info-card--purple">
|
||||
<div class="txn-info-card__icon"><fa-icon icon="clock"></fa-icon></div>
|
||||
<div class="txn-info-card__body">
|
||||
<div class="txn-info-card__label">Submitted On</div>
|
||||
<div class="txn-info-card__value txn-info-card__value--small">
|
||||
{{ dataSource.data[0].submittedOnDate | datetimeFormat }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="txn-info-card txn-info-card--grey">
|
||||
<div class="txn-info-card__icon"><fa-icon icon="user"></fa-icon></div>
|
||||
<div class="txn-info-card__body">
|
||||
<div class="txn-info-card__label">Created By</div>
|
||||
<div class="txn-info-card__value">{{ dataSource.data[0].createdByUserName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (dataSource.data[0].externalAssetOwner) {
|
||||
<div class="txn-info-card txn-info-card--orange">
|
||||
<div class="txn-info-card__icon"><fa-icon icon="link"></fa-icon></div>
|
||||
<div class="txn-info-card__body">
|
||||
<div class="txn-info-card__label">External Asset Owner</div>
|
||||
<div class="txn-info-card__value">{{ dataSource.data[0].externalAssetOwner }}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- DR/CR Summary Bar -->
|
||||
<div class="txn-summary-bar">
|
||||
<div class="txn-summary-bar__item txn-summary-bar__item--dr">
|
||||
<fa-icon icon="arrow-up" class="m-r-6"></fa-icon>
|
||||
Total Debit: <strong>{{ getTotalDebit() | formatNumber }}</strong>
|
||||
</div>
|
||||
<div class="txn-summary-bar__divider"></div>
|
||||
<div class="txn-summary-bar__item txn-summary-bar__item--cr">
|
||||
<fa-icon icon="arrow-down" class="m-r-6"></fa-icon>
|
||||
Total Credit: <strong>{{ getTotalCredit() | formatNumber }}</strong>
|
||||
</div>
|
||||
<div class="txn-summary-bar__divider"></div>
|
||||
<div
|
||||
class="txn-summary-bar__item"
|
||||
[ngClass]="isBalanced() ? 'txn-summary-bar__item--balanced' : 'txn-summary-bar__item--unbalanced'"
|
||||
>
|
||||
<fa-icon [icon]="isBalanced() ? 'check-circle' : 'exclamation-triangle'" class="m-r-6"></fa-icon>
|
||||
{{ isBalanced() ? 'Balanced' : 'Unbalanced' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="mat-elevation-z8 container layout-align-center layout-row-wrap">
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Entry ID' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let transaction">{{ transaction.id }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountType">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Type' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountType.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountCode">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Account Code' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountCode }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountName">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Account Name' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let transaction">{{ transaction.glAccountName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="debit">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Debit' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let transaction">
|
||||
@if (transaction.entryType.value === 'DEBIT') {
|
||||
<span>
|
||||
{{ transaction.currency.displaySymbol || transaction.currency.code }}
|
||||
{{ transaction.amount | formatNumber }}
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="credit">
|
||||
<th mat-header-cell class="center" *matHeaderCellDef mat-sort-header>
|
||||
{{ 'labels.inputs.Credit' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let transaction">
|
||||
@if (transaction.entryType.value === 'CREDIT') {
|
||||
<span>
|
||||
{{ transaction.currency.displaySymbol || transaction.currency.code }}
|
||||
{{ transaction.amount | formatNumber }}
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
(click)="viewJournalEntry(row)"
|
||||
class="select-row"
|
||||
></tr>
|
||||
</table>
|
||||
<div class="layout-row layout-align-center m-t-15 m-b-15 gap-5px layout-xs-column">
|
||||
<button type="button" color="primary" mat-raised-button (click)="goBack()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Journal Entries Table -->
|
||||
<mat-card class="txn-entries-card mat-elevation-z2">
|
||||
<mat-card-header class="txn-entries-card__header">
|
||||
<mat-card-title>
|
||||
<fa-icon icon="table" class="m-r-6"></fa-icon>
|
||||
Journal Entries
|
||||
<span class="txn-entries-count">{{ dataSource?.data?.length || 0 }} entries</span>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="txn-entries-card__content">
|
||||
<div class="txn-table-wrapper">
|
||||
<table mat-table [dataSource]="dataSource" matSort class="txn-table">
|
||||
<!-- Entry ID -->
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="txn-th"># ID</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td">
|
||||
<span class="txn-entry-id">{{ entry.id }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Account Type -->
|
||||
<ng-container matColumnDef="glAccountType">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="txn-th">Account Type</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td">
|
||||
<span class="txn-acct-type-badge" [ngClass]="getAccountTypeBadgeClass(entry.glAccountType?.value)">
|
||||
{{ entry.glAccountType?.value }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Account Code -->
|
||||
<ng-container matColumnDef="glAccountCode">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="txn-th">Code</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td">
|
||||
<span class="txn-acct-code">{{ entry.glAccountCode }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Account Name -->
|
||||
<ng-container matColumnDef="glAccountName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="txn-th">Account Name</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td">
|
||||
<span class="txn-acct-name">{{ entry.glAccountName }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- DR/CR Badge -->
|
||||
<ng-container matColumnDef="entryType">
|
||||
<th mat-header-cell *matHeaderCellDef class="txn-th txn-th--center">DR / CR</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td txn-td--center">
|
||||
<span
|
||||
class="txn-drcr-badge"
|
||||
[ngClass]="entry.entryType?.value === 'DEBIT' ? 'txn-drcr-badge--dr' : 'txn-drcr-badge--cr'"
|
||||
>
|
||||
{{ entry.entryType?.value === 'DEBIT' ? 'DR' : 'CR' }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Debit -->
|
||||
<ng-container matColumnDef="debit">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="txn-th txn-th--amount txn-th--dr">
|
||||
Debit (Dr)
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td txn-td--amount">
|
||||
@if (entry.entryType?.value === 'DEBIT') {
|
||||
<span class="txn-amount txn-amount--dr">
|
||||
{{ entry.currency?.displaySymbol || entry.currency?.code }}
|
||||
{{ entry.amount | formatNumber }}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="txn-amount--empty">—</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Credit -->
|
||||
<ng-container matColumnDef="credit">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="txn-th txn-th--amount txn-th--cr">
|
||||
Credit (Cr)
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let entry" class="txn-td txn-td--amount">
|
||||
@if (entry.entryType?.value === 'CREDIT') {
|
||||
<span class="txn-amount txn-amount--cr">
|
||||
{{ entry.currency?.displaySymbol || entry.currency?.code }}
|
||||
{{ entry.amount | formatNumber }}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="txn-amount--empty">—</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns" class="txn-header-row"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
(click)="viewJournalEntry(row)"
|
||||
class="txn-data-row"
|
||||
[ngClass]="row.entryType?.value === 'DEBIT' ? 'txn-row--dr' : 'txn-row--cr'"
|
||||
></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="txn-footer-actions">
|
||||
<button mat-stroked-button color="primary" (click)="goBack()">
|
||||
<fa-icon icon="arrow-left" class="m-r-6"></fa-icon> Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!isJournalEntryLoaded) {
|
||||
<div class="container m-b-20">
|
||||
<div class="alert">
|
||||
<div class="message">
|
||||
<i class="fa fa-exclamation-circle alert-check"></i>
|
||||
{{ 'labels.text.No data found' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-row layout-align-center m-t-15 m-b-15 gap-5px layout-xs-column">
|
||||
<button type="button" color="primary" mat-raised-button (click)="goBack()">
|
||||
{{ 'labels.buttons.Back' | translate }}
|
||||
<div class="txn-no-data">
|
||||
<div class="txn-no-data__box">
|
||||
<fa-icon icon="search" size="2x" class="txn-no-data__icon"></fa-icon>
|
||||
<h4>No Journal Entry Found</h4>
|
||||
<p>The transaction you are looking for does not exist or has been removed.</p>
|
||||
<button mat-raised-button color="primary" (click)="goBack()">
|
||||
<fa-icon icon="arrow-left" class="m-r-6"></fa-icon> Go Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+487
-5
@@ -6,13 +6,495 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
@use 'assets/styles/helper';
|
||||
@use 'assets/styles/colours' as *;
|
||||
$dr-color: #e53935;
|
||||
$cr-color: #43a047;
|
||||
$radius: 10px;
|
||||
$shadow: 0 2px 12px rgb(0 0 0 / 8%);
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
.m-r-6 {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.select-row:hover {
|
||||
.m-r-8 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.m-r-4 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
// ─── Page Layout ─────────────────────────────────────────────────────────────
|
||||
.txn-detail-page {
|
||||
padding: 0.5rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.1rem;
|
||||
}
|
||||
|
||||
// ─── Header Bar ───────────────────────────────────────────────────────────────
|
||||
.txn-header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 0;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
&__title-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-text, #212121);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__txn-id {
|
||||
font-size: 0.75rem;
|
||||
color: #78909c;
|
||||
font-family: 'Courier New', monospace;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.txn-back-btn {
|
||||
color: #546e7a;
|
||||
|
||||
&:hover {
|
||||
color: #1976d2;
|
||||
}
|
||||
}
|
||||
|
||||
.txn-revert-btn {
|
||||
border-radius: 8px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
// ─── Info Cards Grid ──────────────────────────────────────────────────────────
|
||||
.txn-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.txn-info-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.85rem;
|
||||
padding: 0.9rem 1.1rem;
|
||||
border-radius: $radius;
|
||||
background: #fff;
|
||||
box-shadow: $shadow;
|
||||
border-left: 4px solid #e0e0e0;
|
||||
transition: transform 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 9px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1rem;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
background: #90a4ae;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #9e9e9e;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: #212121;
|
||||
|
||||
&--small {
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
}
|
||||
|
||||
&--manual {
|
||||
border-left-color: #f57f17;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #f57f17, #ffb74d);
|
||||
}
|
||||
}
|
||||
|
||||
&--system {
|
||||
border-left-color: #3949ab;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #3949ab, #7986cb);
|
||||
}
|
||||
}
|
||||
|
||||
&--blue {
|
||||
border-left-color: #1976d2;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #1976d2, #42a5f5);
|
||||
}
|
||||
}
|
||||
|
||||
&--teal {
|
||||
border-left-color: #00796b;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #00796b, #4db6ac);
|
||||
}
|
||||
}
|
||||
|
||||
&--purple {
|
||||
border-left-color: #6a1b9a;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #6a1b9a, #ab47bc);
|
||||
}
|
||||
}
|
||||
|
||||
&--grey {
|
||||
border-left-color: #546e7a;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #546e7a, #90a4ae);
|
||||
}
|
||||
}
|
||||
|
||||
&--orange {
|
||||
border-left-color: #e65100;
|
||||
|
||||
.txn-info-card__icon {
|
||||
background: linear-gradient(135deg, #e65100, #ff8a65);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Summary Bar ──────────────────────────────────────────────────────────────
|
||||
.txn-summary-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
padding: 0.85rem 1.25rem;
|
||||
background: #fff;
|
||||
border-radius: $radius;
|
||||
box-shadow: $shadow;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__item {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
strong {
|
||||
margin-left: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&--dr {
|
||||
color: $dr-color;
|
||||
}
|
||||
|
||||
&--cr {
|
||||
color: $cr-color;
|
||||
}
|
||||
|
||||
&--balanced {
|
||||
color: #2e7d32;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&--unbalanced {
|
||||
color: #c62828;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Entries Card ─────────────────────────────────────────────────────────────
|
||||
.txn-entries-card {
|
||||
border-radius: $radius !important;
|
||||
box-shadow: $shadow !important;
|
||||
border: 1px solid var(--border-color, #e0e0e0);
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
padding: 0.85rem 1.25rem 0 !important;
|
||||
|
||||
mat-card-title {
|
||||
font-size: 0.95rem !important;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--primary-text, #424242);
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.txn-entries-count {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
padding: 2px 9px;
|
||||
border-radius: 20px;
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
// ─── Table ────────────────────────────────────────────────────────────────────
|
||||
.txn-table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.txn-table {
|
||||
width: 100%;
|
||||
|
||||
.txn-header-row {
|
||||
background: #f5f7fa;
|
||||
|
||||
th {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #616161;
|
||||
padding: 0.7rem 1rem;
|
||||
white-space: nowrap;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
.txn-data-row {
|
||||
cursor: pointer;
|
||||
transition: background 0.1s ease;
|
||||
|
||||
td {
|
||||
padding: 0.65rem 1rem;
|
||||
font-size: 0.84rem;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f8f9ff !important;
|
||||
}
|
||||
|
||||
&.txn-row--dr td:first-child {
|
||||
border-left: 3px solid rgba($dr-color, 0.4);
|
||||
}
|
||||
|
||||
&.txn-row--cr td:first-child {
|
||||
border-left: 3px solid rgba($cr-color, 0.4);
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.txn-th--center,
|
||||
.txn-td--center {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.txn-th--amount {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.txn-td--amount {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
.txn-th--dr {
|
||||
color: $dr-color !important;
|
||||
}
|
||||
|
||||
.txn-th--cr {
|
||||
color: $cr-color !important;
|
||||
}
|
||||
|
||||
// ─── Cell Elements ────────────────────────────────────────────────────────────
|
||||
.txn-entry-id {
|
||||
font-weight: 600;
|
||||
font-size: 0.78rem;
|
||||
color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
padding: 2px 8px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.txn-acct-code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: #455a64;
|
||||
background: #eceff1;
|
||||
padding: 2px 7px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.txn-acct-name {
|
||||
font-size: 0.84rem;
|
||||
font-weight: 500;
|
||||
color: #37474f;
|
||||
}
|
||||
|
||||
// ─── Badges ───────────────────────────────────────────────────────────────────
|
||||
.txn-acct-type-badge {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
padding: 3px 9px;
|
||||
border-radius: 20px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
|
||||
&.txn-badge--asset {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
&.txn-badge--liability {
|
||||
background: #f3e5f5;
|
||||
color: #6a1b9a;
|
||||
}
|
||||
|
||||
&.txn-badge--income {
|
||||
background: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
&.txn-badge--expense {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
&.txn-badge--equity {
|
||||
background: #fff3e0;
|
||||
color: #e65100;
|
||||
}
|
||||
|
||||
&.txn-badge--default {
|
||||
background: #f5f5f5;
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
|
||||
.txn-drcr-badge {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
padding: 3px 11px;
|
||||
border-radius: 20px;
|
||||
letter-spacing: 0.8px;
|
||||
|
||||
&--dr {
|
||||
background: #ffebee;
|
||||
color: $dr-color;
|
||||
border: 1px solid rgba($dr-color, 0.3);
|
||||
}
|
||||
|
||||
&--cr {
|
||||
background: #e8f5e9;
|
||||
color: $cr-color;
|
||||
border: 1px solid rgba($cr-color, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Amounts ──────────────────────────────────────────────────────────────────
|
||||
.txn-amount {
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
white-space: nowrap;
|
||||
|
||||
&--dr {
|
||||
color: $dr-color;
|
||||
}
|
||||
|
||||
&--cr {
|
||||
color: $cr-color;
|
||||
}
|
||||
}
|
||||
|
||||
.txn-amount--empty {
|
||||
color: #bdbdbd;
|
||||
}
|
||||
|
||||
// ─── Footer ───────────────────────────────────────────────────────────────────
|
||||
.txn-footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 0.25rem 0 0.5rem;
|
||||
}
|
||||
|
||||
// ─── No Data ──────────────────────────────────────────────────────────────────
|
||||
.txn-no-data {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
|
||||
&__box {
|
||||
text-align: center;
|
||||
padding: 2.5rem;
|
||||
border-radius: $radius;
|
||||
background: #fff;
|
||||
box-shadow: $shadow;
|
||||
max-width: 400px;
|
||||
|
||||
h4 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: #424242;
|
||||
margin: 0.75rem 0 0.4rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.85rem;
|
||||
color: #757575;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
+37
-33
@@ -27,13 +27,15 @@ import {
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { Location } from '@angular/common';
|
||||
import { Location, NgClass } from '@angular/common';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
import { DatetimeFormatPipe } from '../../../pipes/datetime-format.pipe';
|
||||
import { FormatNumberPipe } from '../../../pipes/format-number.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { YesnoPipe } from '@pipes/yesno.pipe';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-view-journal-entry-transaction',
|
||||
@@ -54,10 +56,13 @@ import { YesnoPipe } from '@pipes/yesno.pipe';
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
NgClass,
|
||||
DateFormatPipe,
|
||||
DatetimeFormatPipe,
|
||||
FormatNumberPipe,
|
||||
YesnoPipe
|
||||
YesnoPipe,
|
||||
MatCardModule,
|
||||
MatTooltipModule
|
||||
]
|
||||
})
|
||||
export class ViewJournalEntryTransactionComponent implements OnInit {
|
||||
@@ -69,34 +74,27 @@ export class ViewJournalEntryTransactionComponent implements OnInit {
|
||||
|
||||
title: string;
|
||||
journalEntriesData: any[];
|
||||
/** Transaction data. */
|
||||
transaction: any;
|
||||
/** Transaction ID. */
|
||||
transactionId: string;
|
||||
/** Columns to be displayed in transaction table. */
|
||||
|
||||
displayedColumns: string[] = [
|
||||
'id',
|
||||
'glAccountType',
|
||||
'glAccountCode',
|
||||
'glAccountName',
|
||||
'entryType',
|
||||
'debit',
|
||||
'credit'
|
||||
];
|
||||
/** Data source for transaction table. */
|
||||
|
||||
dataSource: MatTableDataSource<any>;
|
||||
|
||||
/** Paginator for transaction table. */
|
||||
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
|
||||
/** Sorter for transaction table. */
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
|
||||
isJournalEntryLoaded = false;
|
||||
|
||||
isManualJournalEntry = false;
|
||||
|
||||
/**
|
||||
* Retrieves the transaction data from `resolve` and sets the transaction table.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.route.data.subscribe((data: { title: string; transaction: any; transferJournalEntryData: any }) => {
|
||||
this.title = data.title;
|
||||
@@ -119,14 +117,10 @@ export class ViewJournalEntryTransactionComponent implements OnInit {
|
||||
isViewTransaction(): boolean {
|
||||
return this.title === 'View Transaction';
|
||||
}
|
||||
|
||||
isViewTransfer(): boolean {
|
||||
return this.title === 'View Transfer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the data source for transaction table with journal entries, paginator and sorter.
|
||||
*/
|
||||
setTransaction() {
|
||||
if (this.journalEntriesData != null) {
|
||||
this.dataSource = new MatTableDataSource(this.journalEntriesData);
|
||||
@@ -149,23 +143,13 @@ export class ViewJournalEntryTransactionComponent implements OnInit {
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* View details of selected journal entry.
|
||||
* @param {any} journalEntry Selected journal entry.
|
||||
*/
|
||||
viewJournalEntry(journalEntry: any) {
|
||||
this.dialog.open(ViewJournalEntryComponent, {
|
||||
data: { journalEntry: journalEntry }
|
||||
});
|
||||
this.dialog.open(ViewJournalEntryComponent, { data: { journalEntry } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the given transaction and redirects to reverted transaction.
|
||||
* @param {transactionId} transactionId Transaction ID of transaction to be reverted.
|
||||
*/
|
||||
revertTransaction(transactionId?: string) {
|
||||
const revertTransactionDialogRef = this.dialog.open(RevertTransactionComponent, {
|
||||
data: { reverted: this.dataSource.data[0].reversed, transactionId: transactionId }
|
||||
data: { reverted: this.dataSource.data[0].reversed, transactionId }
|
||||
});
|
||||
revertTransactionDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.revert) {
|
||||
@@ -191,10 +175,30 @@ export class ViewJournalEntryTransactionComponent implements OnInit {
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
journalEntryColor(): string {
|
||||
if (this.isManualJournalEntry) {
|
||||
return 'manual-entry';
|
||||
}
|
||||
return '';
|
||||
getTotalDebit(): number {
|
||||
return (this.dataSource?.data || [])
|
||||
.filter((e) => e.entryType?.value === 'DEBIT')
|
||||
.reduce((sum, e) => sum + (e.amount || 0), 0);
|
||||
}
|
||||
|
||||
getTotalCredit(): number {
|
||||
return (this.dataSource?.data || [])
|
||||
.filter((e) => e.entryType?.value === 'CREDIT')
|
||||
.reduce((sum, e) => sum + (e.amount || 0), 0);
|
||||
}
|
||||
|
||||
isBalanced(): boolean {
|
||||
return Math.abs(this.getTotalDebit() - this.getTotalCredit()) < 0.01;
|
||||
}
|
||||
|
||||
getAccountTypeBadgeClass(type: string): string {
|
||||
const map: Record<string, string> = {
|
||||
ASSET: 'txn-badge--asset',
|
||||
LIABILITY: 'txn-badge--liability',
|
||||
INCOME: 'txn-badge--income',
|
||||
EXPENSE: 'txn-badge--expense',
|
||||
EQUITY: 'txn-badge--equity'
|
||||
};
|
||||
return map[type] || 'txn-badge--default';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,25 @@ import {
|
||||
faRobot,
|
||||
faMicrochip,
|
||||
faMagic,
|
||||
faQrcode
|
||||
faQrcode,
|
||||
faCalendarDay,
|
||||
faCalendarWeek,
|
||||
faChartArea,
|
||||
faChevronUp,
|
||||
faWallet,
|
||||
faExchangeAlt,
|
||||
faArrowsAltH,
|
||||
faSignInAlt,
|
||||
faDoorOpen,
|
||||
faBoxOpen,
|
||||
faToggleOn,
|
||||
faToggleOff,
|
||||
faChartColumn,
|
||||
faMoneyBillTrendUp,
|
||||
faScaleBalanced,
|
||||
faNotesMedical,
|
||||
faFileLines,
|
||||
faBuildingColumns
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
/**
|
||||
@@ -393,7 +411,25 @@ export class IconsModule {
|
||||
faRobot,
|
||||
faMicrochip,
|
||||
faMagic,
|
||||
faQrcode
|
||||
faQrcode,
|
||||
faCalendarDay,
|
||||
faCalendarWeek,
|
||||
faChartArea,
|
||||
faChevronUp,
|
||||
faWallet,
|
||||
faExchangeAlt,
|
||||
faArrowsAltH,
|
||||
faSignInAlt,
|
||||
faDoorOpen,
|
||||
faBoxOpen,
|
||||
faToggleOn,
|
||||
faToggleOff,
|
||||
faChartColumn,
|
||||
faMoneyBillTrendUp,
|
||||
faScaleBalanced,
|
||||
faNotesMedical,
|
||||
faFileLines,
|
||||
faBuildingColumns
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user