Files
cbsnexora/src/app/accounting/search-journal-entry/search-journal-entry.component.html
T
nitesh ee5a430b17
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
Merge branch 'dev' of https://git.lincpay.tech/CBSNexora/cbsnexora into dev
# Conflicts:
#	src/environments/.env.ts
2026-09-14 12:04:24 +05:30

741 lines
32 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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/.
-->
<!-- ══════════════════════════════════════════════════════════
HERO HEADER — glassmorphism + animated gradient
══════════════════════════════════════════════════════════ -->
<div class="gl-hero">
<div class="gl-hero__bg"></div>
<div class="gl-hero__content">
<div class="gl-hero__left">
<div class="gl-hero__icon-wrap">
<fa-icon icon="building-columns" class="gl-hero__icon"></fa-icon>
<div class="gl-hero__icon-pulse"></div>
</div>
<div class="gl-hero__text">
<div class="gl-hero__breadcrumb">
<fa-icon icon="home" class="gl-hero__bc-icon"></fa-icon>
<span>Accounting</span>
<fa-icon icon="chevron-right" class="gl-hero__bc-sep"></fa-icon>
<span class="gl-hero__bc-active">General Ledger</span>
</div>
<h1 class="gl-hero__title">General Ledger</h1>
<p class="gl-hero__subtitle" *ngIf="!glAccountInfo">
Full double-entry journal — view, filter &amp; analyse all accounting entries
</p>
<div class="gl-hero__acct-pill" *ngIf="glAccountInfo">
<span [class]="'gl-pill gl-pill--' + (glAccountInfo.type?.value | lowercase)">
<fa-icon [icon]="getAccountTypeIcon(glAccountInfo.type?.value)" class="gl-pill__icon"></fa-icon>
{{ glAccountInfo.type?.value }}
</span>
<span class="gl-hero__acct-code">{{ glAccountInfo.glCode }}</span>
<span class="gl-hero__acct-sep">·</span>
<span class="gl-hero__acct-name">{{ glAccountInfo.name }}</span>
</div>
</div>
</div>
<div class="gl-hero__actions">
<div class="gl-hero__meta" *ngIf="ledgerData?.fromDate || ledgerData?.toDate">
<fa-icon icon="calendar-alt" class="gl-hero__meta-icon"></fa-icon>
<span>{{ formatLedgerDate(ledgerData?.fromDate) }} → {{ formatLedgerDate(ledgerData?.toDate) }}</span>
</div>
<button
class="gl-hero-btn gl-hero-btn--ghost"
(click)="loadLedgerSummary(); loadJournalEntriesPage()"
matTooltip="Refresh data"
>
<fa-icon icon="sync" [class.fa-spin]="ledgerLoading"></fa-icon>
Refresh
</button>
<!-- New Entry — hidden while Create Journal Entries is disabled, see disabled-features.ts -->
@if (!disabledFeatures.createJournalEntries) {
<button class="gl-hero-btn gl-hero-btn--primary" [routerLink]="['/accounting/journal-entries/create']">
<fa-icon icon="plus"></fa-icon>
New Entry
</button>
}
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
LOADING SKELETON / PROGRESS BAR
══════════════════════════════════════════════════════════ -->
<div class="gl-progress-bar" *ngIf="ledgerLoading">
<div class="gl-progress-bar__fill"></div>
</div>
<!-- ══════════════════════════════════════════════════════════
GL ACCOUNT DETAIL STRIP (visible when account selected)
══════════════════════════════════════════════════════════ -->
<div class="gl-acct-strip" *ngIf="glAccountInfo">
<div class="gl-acct-strip__item">
<div class="gl-acct-strip__label">Account Code</div>
<div class="gl-acct-strip__value gl-mono">{{ glAccountInfo.glCode }}</div>
</div>
<div class="gl-acct-strip__divider"></div>
<div class="gl-acct-strip__item">
<div class="gl-acct-strip__label">Account Name</div>
<div class="gl-acct-strip__value">{{ glAccountInfo.name }}</div>
</div>
<div class="gl-acct-strip__divider"></div>
<div class="gl-acct-strip__item">
<div class="gl-acct-strip__label">Classification</div>
<div class="gl-acct-strip__value">
<span [class]="'gl-pill gl-pill--sm gl-pill--' + (glAccountInfo.type?.value | lowercase)">
<fa-icon [icon]="getAccountTypeIcon(glAccountInfo.type?.value)" class="gl-pill__icon"></fa-icon>
{{ glAccountInfo.type?.value }}
</span>
</div>
</div>
<div class="gl-acct-strip__divider"></div>
<div class="gl-acct-strip__item">
<div class="gl-acct-strip__label">Usage</div>
<div class="gl-acct-strip__value">{{ glAccountInfo.usage?.value || '—' }}</div>
</div>
<div class="gl-acct-strip__divider"></div>
<div class="gl-acct-strip__item">
<div class="gl-acct-strip__label">Manual Entries</div>
<div class="gl-acct-strip__value">
<span [class]="glAccountInfo.manualEntriesAllowed ? 'gl-status gl-status--on' : 'gl-status gl-status--off'">
<fa-icon [icon]="glAccountInfo.manualEntriesAllowed ? 'check-circle' : 'times-circle'"></fa-icon>
{{ glAccountInfo.manualEntriesAllowed ? 'Allowed' : 'Restricted' }}
</span>
</div>
</div>
<div class="gl-acct-strip__divider" *ngIf="glAccountInfo.description"></div>
<div class="gl-acct-strip__item gl-acct-strip__item--desc" *ngIf="glAccountInfo.description">
<div class="gl-acct-strip__label">Description</div>
<div class="gl-acct-strip__value gl-acct-strip__value--muted">{{ glAccountInfo.description }}</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
KPI CARDS — 6 metrics with micro bar indicators
══════════════════════════════════════════════════════════ -->
<div class="gl-kpi-grid" [class.gl-kpi-grid--loading]="ledgerLoading">
<!-- Opening Balance -->
<div class="gl-kpi gl-kpi--opening">
<div class="gl-kpi__header">
<div class="gl-kpi__icon-box">
<fa-icon icon="door-open"></fa-icon>
</div>
<div class="gl-kpi__trend gl-kpi__trend--neutral" matTooltip="Balance before the selected period">
<fa-icon icon="info-circle"></fa-icon>
</div>
</div>
<div class="gl-kpi__body">
<div class="gl-kpi__label">Opening Balance</div>
<div class="gl-kpi__value" [class.gl-kpi__value--neg]="openingBalance < 0">
{{ openingBalance | formatNumber }}
</div>
<div class="gl-kpi__caption">Before {{ formatLedgerDate(ledgerData?.fromDate) || 'period start' }}</div>
</div>
<div class="gl-kpi__bar gl-kpi__bar--opening"></div>
</div>
<!-- Total Debit -->
<div class="gl-kpi gl-kpi--dr">
<div class="gl-kpi__header">
<div class="gl-kpi__icon-box">
<fa-icon icon="arrow-up"></fa-icon>
</div>
<div class="gl-kpi__trend gl-kpi__trend--dr" matTooltip="Sum of all debit entries in period">
<fa-icon icon="arrow-up"></fa-icon> DR
</div>
</div>
<div class="gl-kpi__body">
<div class="gl-kpi__label">Total Debit</div>
<div class="gl-kpi__value gl-kpi__value--dr">{{ totalDebit | formatNumber }}</div>
<div class="gl-kpi__caption">Debit entries in period</div>
</div>
<div class="gl-kpi__bar gl-kpi__bar--dr"></div>
</div>
<!-- Total Credit -->
<div class="gl-kpi gl-kpi--cr">
<div class="gl-kpi__header">
<div class="gl-kpi__icon-box">
<fa-icon icon="arrow-down"></fa-icon>
</div>
<div class="gl-kpi__trend gl-kpi__trend--cr" matTooltip="Sum of all credit entries in period">
<fa-icon icon="arrow-down"></fa-icon> CR
</div>
</div>
<div class="gl-kpi__body">
<div class="gl-kpi__label">Total Credit</div>
<div class="gl-kpi__value gl-kpi__value--cr">{{ totalCredit | formatNumber }}</div>
<div class="gl-kpi__caption">Credit entries in period</div>
</div>
<div class="gl-kpi__bar gl-kpi__bar--cr"></div>
</div>
<!-- Net Movement -->
<div class="gl-kpi gl-kpi--net">
<div class="gl-kpi__header">
<div class="gl-kpi__icon-box">
<fa-icon icon="scale-balanced"></fa-icon>
</div>
<div
[class]="netMovementPositive ? 'gl-kpi__trend gl-kpi__trend--pos' : 'gl-kpi__trend gl-kpi__trend--neg'"
[matTooltip]="isDebitNormal ? 'DR − CR (Debit-normal account)' : 'CR − DR (Credit-normal account)'"
>
<fa-icon [icon]="netMovementPositive ? 'arrow-up' : 'arrow-down'"></fa-icon>
<span class="gl-kpi__norm-tag">{{ isDebitNormal ? 'DR−CR' : 'CR−DR' }}</span>
</div>
</div>
<div class="gl-kpi__body">
<div class="gl-kpi__label">Net Movement</div>
<div
class="gl-kpi__value"
[class.gl-kpi__value--pos]="netMovementPositive"
[class.gl-kpi__value--neg]="!netMovementPositive"
>
{{ netMovement | formatNumber }}
</div>
<div class="gl-kpi__caption">
{{ netMovementPositive ? 'Net Positive' : 'Net Negative' }} ·
{{ isDebitNormal ? 'Debit Normal' : 'Credit Normal' }}
</div>
</div>
<div class="gl-kpi__bar gl-kpi__bar--net"></div>
</div>
<!-- Closing Balance -->
<div class="gl-kpi gl-kpi--closing">
<div class="gl-kpi__header">
<div class="gl-kpi__icon-box">
<fa-icon icon="box-open"></fa-icon>
</div>
<div
[class]="closingBalance >= 0 ? 'gl-kpi__trend gl-kpi__trend--pos' : 'gl-kpi__trend gl-kpi__trend--neg'"
matTooltip="Opening + Net Movement"
>
<fa-icon [icon]="closingBalance >= 0 ? 'arrow-up' : 'arrow-down'"></fa-icon>
</div>
</div>
<div class="gl-kpi__body">
<div class="gl-kpi__label">Closing Balance</div>
<div class="gl-kpi__value" [class.gl-kpi__value--neg]="closingBalance < 0">
{{ closingBalance | formatNumber }}
</div>
<div class="gl-kpi__caption">As of {{ formatLedgerDate(ledgerData?.toDate) || 'period end' }}</div>
</div>
<div class="gl-kpi__bar gl-kpi__bar--closing"></div>
</div>
<!-- Entry Count -->
<div class="gl-kpi gl-kpi--count">
<div class="gl-kpi__header">
<div class="gl-kpi__icon-box">
<fa-icon icon="receipt"></fa-icon>
</div>
<div class="gl-kpi__trend gl-kpi__trend--neutral" matTooltip="Total filtered records">
<fa-icon icon="filter"></fa-icon>
</div>
</div>
<div class="gl-kpi__body">
<div class="gl-kpi__label">Total Entries</div>
<div class="gl-kpi__value gl-kpi__value--count">{{ totalEntries || (dataSource?.records$ | async) || 0 }}</div>
<div class="gl-kpi__caption">Matching journal entries</div>
</div>
<div class="gl-kpi__bar gl-kpi__bar--count"></div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
QUICK DATE CHIPS
══════════════════════════════════════════════════════════ -->
<div class="gl-quick-bar">
<div class="gl-quick-bar__label"><fa-icon icon="clock"></fa-icon> Quick Range</div>
<div class="gl-quick-bar__chips">
<button
*ngFor="let q of quickFilters"
[class.active]="activeQuickFilter === q.label"
class="gl-qchip"
(click)="applyQuickFilter(q)"
>
<fa-icon [icon]="q.icon"></fa-icon>
{{ q.label }}
</button>
<button *ngIf="activeQuickFilter" class="gl-qchip gl-qchip--clear" (click)="clearQuickFilter()">
<fa-icon icon="times"></fa-icon> Clear
</button>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
FILTER PANEL
══════════════════════════════════════════════════════════ -->
<div class="gl-filter-panel">
<!-- Primary filters -->
<div class="gl-filter-panel__primary">
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> From Date</label>
<mat-form-field appearance="outline" class="gl-ffield">
<input matInput [matDatepicker]="dpFrom" [min]="minDate" [max]="maxDate" [formControl]="transactionDateFrom" />
<mat-datepicker-toggle matSuffix [for]="dpFrom"></mat-datepicker-toggle>
<mat-datepicker #dpFrom></mat-datepicker>
</mat-form-field>
</div>
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> To Date</label>
<mat-form-field appearance="outline" class="gl-ffield">
<input matInput [matDatepicker]="dpTo" [min]="minDate" [max]="maxDate" [formControl]="transactionDateTo" />
<mat-datepicker-toggle matSuffix [for]="dpTo"></mat-datepicker-toggle>
<mat-datepicker #dpTo></mat-datepicker>
</mat-form-field>
</div>
<div class="gl-fgroup gl-fgroup--wide">
<label class="gl-flabel"><fa-icon icon="building-columns"></fa-icon> GL Account</label>
<div class="gl-ffield gl-ffield--selector">
<mifosx-gl-account-selector
[inputFormControl]="glAccount"
[glAccountList]="glAccountData"
></mifosx-gl-account-selector>
</div>
</div>
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="toggle-on"></fa-icon> Entry Source</label>
<mat-form-field appearance="outline" class="gl-ffield">
<mat-select [formControl]="entryTypeFilter">
<mat-option *ngFor="let et of entryTypeFilterData" [value]="et.value">{{ et.option }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="gl-filter-actions">
<button
class="gl-fab gl-fab--adv"
(click)="showAdvancedFilters = !showAdvancedFilters"
[class.gl-fab--adv-on]="showAdvancedFilters"
matTooltip="More filters"
>
<fa-icon icon="sliders-h"></fa-icon>
<span *ngIf="activeFiltersCount > 0" class="gl-fab__badge">{{ activeFiltersCount }}</span>
</button>
<button class="gl-fab gl-fab--reset" (click)="clearFilters()" matTooltip="Reset all filters">
<fa-icon icon="times"></fa-icon>
</button>
</div>
</div>
<!-- Advanced filters (collapsible) -->
<div class="gl-filter-panel__advanced" [class.gl-filter-panel__advanced--open]="showAdvancedFilters">
<div class="gl-filter-panel__adv-inner">
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="university"></fa-icon> Office</label>
<mat-form-field appearance="outline" class="gl-ffield">
<input matInput [formControl]="officeName" [matAutocomplete]="officeAc" placeholder="Search office…" />
<mat-autocomplete #officeAc="matAutocomplete" [displayWith]="displayOfficeName">
<mat-option *ngFor="let o of filteredOfficeData | async" [value]="o">{{ o.name }}</mat-option>
</mat-autocomplete>
</mat-form-field>
</div>
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="hashtag"></fa-icon> Transaction ID</label>
<mat-form-field appearance="outline" class="gl-ffield">
<input matInput [formControl]="transactionIdCtrl" placeholder="e.g. L000001" />
<fa-icon icon="search" matSuffix class="gl-field-suffix-icon"></fa-icon>
</mat-form-field>
</div>
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-check"></fa-icon> Submitted From</label>
<mat-form-field appearance="outline" class="gl-ffield">
<input
matInput
[matDatepicker]="dpSubFrom"
[min]="minDate"
[max]="maxDate"
[formControl]="submittedOnDateFrom"
/>
<mat-datepicker-toggle matSuffix [for]="dpSubFrom"></mat-datepicker-toggle>
<mat-datepicker #dpSubFrom></mat-datepicker>
</mat-form-field>
</div>
<div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-check"></fa-icon> Submitted To</label>
<mat-form-field appearance="outline" class="gl-ffield">
<input matInput [matDatepicker]="dpSubTo" [min]="minDate" [max]="maxDate" [formControl]="submittedOnDateTo" />
<mat-datepicker-toggle matSuffix [for]="dpSubTo"></mat-datepicker-toggle>
<mat-datepicker #dpSubTo></mat-datepicker>
</mat-form-field>
</div>
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
TIP CARD — shown when no GL account selected
══════════════════════════════════════════════════════════ -->
<div class="gl-tip" *ngIf="!hasGlAccountFilter">
<div class="gl-tip__icon"><fa-icon icon="lightbulb"></fa-icon></div>
<div class="gl-tip__body">
<strong>Select a GL Account</strong> to unlock full ledger details — opening balance, day-wise running balance,
closing balance, and per-entry running totals.
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
DAY-WISE BALANCE TIMELINE
══════════════════════════════════════════════════════════ -->
<div class="gl-timeline-section" *ngIf="dayWiseData.length > 0">
<div class="gl-section-head">
<div class="gl-section-head__left">
<div class="gl-section-head__dot gl-section-head__dot--purple"></div>
<span class="gl-section-head__title">Day-wise Balance Timeline</span>
<span class="gl-section-head__badge">{{ dayWiseData.length }} days</span>
</div>
<div class="gl-section-head__right">
<span class="gl-legend-pill gl-legend-pill--open">Opening</span>
<span class="gl-legend-pill gl-legend-pill--dr">Debit</span>
<span class="gl-legend-pill gl-legend-pill--cr">Credit</span>
<span class="gl-legend-pill gl-legend-pill--net">Net</span>
<span class="gl-legend-pill gl-legend-pill--close">Closing</span>
</div>
</div>
<div class="gl-timeline-wrap">
<table class="gl-tl-table">
<thead>
<tr>
<th class="gl-tl-th">Date</th>
<th class="gl-tl-th gl-tl-th--num">Opening Bal</th>
<th class="gl-tl-th gl-tl-th--num gl-tl-th--dr">Debit (DR)</th>
<th class="gl-tl-th gl-tl-th--num gl-tl-th--cr">Credit (CR)</th>
<th class="gl-tl-th gl-tl-th--bar">Movement</th>
<th class="gl-tl-th gl-tl-th--num">Net</th>
<th class="gl-tl-th gl-tl-th--num gl-tl-th--close">Closing Bal</th>
<th class="gl-tl-th gl-tl-th--ctr">Entries</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="let row of dayWiseData; let i = index"
class="gl-tl-row"
[class.gl-tl-row--first]="i === 0"
[class.gl-tl-row--last]="i === dayWiseData.length - 1"
>
<td class="gl-tl-date">
<div class="gl-tl-date__inner">
<div
class="gl-tl-date__dot"
[class.gl-tl-date__dot--up]="row.netMovement >= 0"
[class.gl-tl-date__dot--down]="row.netMovement < 0"
></div>
<div class="gl-tl-date__text">{{ formatLedgerDate(row.date) }}</div>
</div>
</td>
<td class="gl-tl-num">{{ row.openingBalance | formatNumber }}</td>
<td class="gl-tl-num gl-tl-num--dr">
<span *ngIf="row.totalDebit > 0">{{ row.totalDebit | formatNumber }}</span>
<span *ngIf="row.totalDebit === 0" class="gl-tl-nil">—</span>
</td>
<td class="gl-tl-num gl-tl-num--cr">
<span *ngIf="row.totalCredit > 0">{{ row.totalCredit | formatNumber }}</span>
<span *ngIf="row.totalCredit === 0" class="gl-tl-nil">—</span>
</td>
<!-- Mini visual bar — DR vs CR side by side as % of their combined total -->
<td class="gl-tl-bar-cell">
<div class="gl-minibar">
<div
class="gl-minibar__dr"
[style.flex]="row.totalDebit"
matTooltip="DR: {{ row.totalDebit | formatNumber }}"
></div>
<div
class="gl-minibar__cr"
[style.flex]="row.totalCredit"
matTooltip="CR: {{ row.totalCredit | formatNumber }}"
></div>
</div>
</td>
<td
class="gl-tl-num"
[class.gl-tl-num--pos]="row.netMovement >= 0"
[class.gl-tl-num--neg]="row.netMovement < 0"
>
<fa-icon [icon]="row.netMovement >= 0 ? 'arrow-up' : 'arrow-down'" class="gl-tl-net-icon"></fa-icon>
{{ row.netMovement | formatNumber }}
</td>
<td class="gl-tl-num gl-tl-num--close" [class.gl-tl-num--neg]="row.closingBalance < 0">
<strong>{{ row.closingBalance | formatNumber }}</strong>
</td>
<td class="gl-tl-ctr">
<span class="gl-tl-badge">{{ row.entryCount }}</span>
</td>
</tr>
</tbody>
<tfoot>
<tr class="gl-tl-footer">
<td class="gl-tl-footer__label">Period Total</td>
<td class="gl-tl-num">{{ openingBalance | formatNumber }}</td>
<td class="gl-tl-num gl-tl-num--dr">
<strong>{{ totalDebit | formatNumber }}</strong>
</td>
<td class="gl-tl-num gl-tl-num--cr">
<strong>{{ totalCredit | formatNumber }}</strong>
</td>
<td></td>
<td
class="gl-tl-num"
[class.gl-tl-num--pos]="netMovementPositive"
[class.gl-tl-num--neg]="!netMovementPositive"
>
<strong>{{ netMovement | formatNumber }}</strong>
</td>
<td class="gl-tl-num gl-tl-num--close" [class.gl-tl-num--neg]="closingBalance < 0">
<strong>{{ closingBalance | formatNumber }}</strong>
</td>
<td class="gl-tl-ctr">
<span class="gl-tl-badge gl-tl-badge--total">{{ (dataSource?.records$ | async) || 0 }}</span>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════
JOURNAL ENTRIES TABLE
══════════════════════════════════════════════════════════ -->
<div class="gl-entries-section">
<div class="gl-section-head">
<div class="gl-section-head__left">
<div class="gl-section-head__dot gl-section-head__dot--blue"></div>
<span class="gl-section-head__title">Journal Entries</span>
<span class="gl-section-head__badge">{{ (dataSource?.records$ | async) || 0 }}</span>
</div>
<div class="gl-section-head__right">
<div class="gl-entries-totals">
<span class="gl-entries-totals__dr">
<fa-icon icon="arrow-up"></fa-icon> DR <strong>{{ totalDebit | formatNumber }}</strong>
</span>
<div class="gl-entries-totals__sep"></div>
<span class="gl-entries-totals__cr">
<fa-icon icon="arrow-down"></fa-icon> CR <strong>{{ totalCredit | formatNumber }}</strong>
</span>
</div>
</div>
</div>
<div class="gl-table-shell">
<mat-table [dataSource]="dataSource" matSort class="gl-mat-table">
<!-- #ID -->
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">#</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<a [routerLink]="['/accounting/journal-entries/transactions/view', e.transactionId]" class="gl-id-link"
>#{{ e.id }}</a
>
</mat-cell>
</ng-container>
<!-- Transaction Date -->
<ng-container matColumnDef="transactionDate">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Date</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<div class="gl-date-cell">
<div class="gl-date-cell__main">{{ formatLedgerDate(e.transactionDate) }}</div>
</div>
</mat-cell>
</ng-container>
<!-- Transaction ID -->
<ng-container matColumnDef="transactionId">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Txn ID</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<a
[routerLink]="['/accounting/journal-entries/transactions/view', e.transactionId]"
class="gl-txn-link gl-mono"
matTooltip="{{ e.transactionId }}"
>
{{ e.transactionId }}
</a>
</mat-cell>
</ng-container>
<!-- Savings Account No -->
<ng-container matColumnDef="savingsAccountNo">
<mat-header-cell *matHeaderCellDef class="gl-th">Account No</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span *ngIf="e.savingsAccountNo" class="gl-acct-no-chip" [matTooltip]="e.savingsAccountNo">{{
e.savingsAccountNo
}}</span>
<span *ngIf="!e.savingsAccountNo" class="gl-na">—</span>
</mat-cell>
</ng-container>
<!-- Office -->
<ng-container matColumnDef="officeName">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Office</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<div class="gl-office-cell">
<fa-icon icon="university" class="gl-office-icon"></fa-icon>
{{ e.officeName }}
</div>
</mat-cell>
</ng-container>
<!-- GL Code -->
<ng-container matColumnDef="glAccountCode">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Code</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span class="gl-code-chip">{{ e.glAccountCode }}</span>
</mat-cell>
</ng-container>
<!-- GL Account Name -->
<ng-container matColumnDef="glAccountName">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Account</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span class="gl-acct-cell" matTooltip="{{ e.glAccountName }}">{{ e.glAccountName }}</span>
</mat-cell>
</ng-container>
<!-- Account Type -->
<ng-container matColumnDef="glAccountType">
<mat-header-cell *matHeaderCellDef class="gl-th">Type</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span [class]="'gl-pill gl-pill--sm gl-pill--' + (e.glAccountType?.value | lowercase)">
<fa-icon [icon]="getAccountTypeIcon(e.glAccountType?.value)" class="gl-pill__icon"></fa-icon>
{{ e.glAccountType?.value }}
</span>
</mat-cell>
</ng-container>
<!-- Entry Type DR/CR -->
<ng-container matColumnDef="entryType">
<mat-header-cell *matHeaderCellDef class="gl-th">Type</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span [class]="e.entryType?.value === 'DEBIT' ? 'gl-drcr gl-drcr--dr' : 'gl-drcr gl-drcr--cr'">
{{ e.entryType?.value === 'DEBIT' ? 'DR' : 'CR' }}
</span>
</mat-cell>
</ng-container>
<!-- Debit -->
<ng-container matColumnDef="debit">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th gl-th--num">Debit</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td gl-td--num">
<span *ngIf="e.entryType?.value === 'DEBIT'" class="gl-amt gl-amt--dr">{{ e.amount | formatNumber }}</span>
<span *ngIf="e.entryType?.value !== 'DEBIT'" class="gl-amt-nil">—</span>
</mat-cell>
</ng-container>
<!-- Credit -->
<ng-container matColumnDef="credit">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th gl-th--num">Credit</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td gl-td--num">
<span *ngIf="e.entryType?.value === 'CREDIT'" class="gl-amt gl-amt--cr">{{ e.amount | formatNumber }}</span>
<span *ngIf="e.entryType?.value !== 'CREDIT'" class="gl-amt-nil">—</span>
</mat-cell>
</ng-container>
<!-- Running Balance -->
<ng-container matColumnDef="runningBalance">
<mat-header-cell *matHeaderCellDef class="gl-th gl-th--num">Running Bal</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td gl-td--num">
<span
class="gl-running"
[class.gl-running--neg]="getRunningBalance(e) < 0"
matTooltip="Office running balance"
>
{{ getRunningBalance(e) | formatNumber }}
</span>
</mat-cell>
</ng-container>
<!-- Source (Manual/System) -->
<ng-container matColumnDef="manualEntry">
<mat-header-cell *matHeaderCellDef class="gl-th">Source</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span [class]="e.manualEntry ? 'gl-src gl-src--manual' : 'gl-src gl-src--system'">
<fa-icon [icon]="e.manualEntry ? 'pencil-alt' : 'cog'"></fa-icon>
{{ e.manualEntry ? 'Manual' : 'System' }}
</span>
</mat-cell>
</ng-container>
<!-- Created By -->
<ng-container matColumnDef="createdByUserName">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">By</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<div class="gl-user-cell">
<div class="gl-user-avatar">{{ (e.createdByUserName || 'U').charAt(0).toUpperCase() }}</div>
<span>{{ e.createdByUserName }}</span>
</div>
</mat-cell>
</ng-container>
<!-- Submitted On -->
<ng-container matColumnDef="submittedOnDate">
<mat-header-cell *matHeaderCellDef mat-sort-header class="gl-th">Submitted</mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<span class="gl-ts">{{ e.submittedOnDate | datetimeFormat }}</span>
</mat-cell>
</ng-container>
<!-- Actions -->
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef class="gl-th"></mat-header-cell>
<mat-cell *matCellDef="let e" class="gl-td">
<a
[routerLink]="['/accounting/journal-entries/transactions/view', e.transactionId]"
class="gl-view-btn"
matTooltip="View transaction"
>
<fa-icon icon="eye"></fa-icon>
</a>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="gl-header-row"></mat-header-row>
<mat-row
*matRowDef="let row; columns: displayedColumns"
[class.gl-data-row--dr]="row.entryType?.value === 'DEBIT'"
[class.gl-data-row--cr]="row.entryType?.value === 'CREDIT'"
class="gl-data-row"
></mat-row>
</mat-table>
<!-- Keyed off the rows actually rendered, not totalEntries: that count comes from the ledger
summary, which only reports a figure once a GL account is chosen, so the search view would
otherwise show "No entries found" above a full table. -->
@if (((dataSource?.entries$ | async) || []).length === 0) {
<div class="gl-empty">
<div class="gl-empty__icon"><fa-icon icon="search"></fa-icon></div>
<div class="gl-empty__title">No entries found</div>
<div class="gl-empty__sub">Try adjusting your filters or date range</div>
</div>
}
<div class="gl-pager-wrap">
<mat-paginator
[length]="(dataSource?.records$ | async) || 0"
[pageSizeOptions]="[10, 25, 50, 100]"
showFirstLastButtons
></mat-paginator>
</div>
</div>
</div>