{{-- ── Card ledger table ────────────────────────────────────── --}}
{{-- Closed-period notice for the account being viewed. Without this the lock
is invisible until a delete fails, which reads as a bug rather than a rule. --}}
@if(!empty($periodClosedThrough))
{{ $selectedBankAccount->name ?? 'This account' }} is closed through
{{ \Illuminate\Support\Carbon::parse($periodClosedThrough)->format('d M Y') }}.
Entries on or before that date cannot be added, edited or deleted. Rows below show a padlock.
Manage closing
{{ $selectedBankAccount ? 'All credits and debits for this account' : $activeTile['subtitle'] }}
{{ $transactions->total() }} tx
{{-- View tabs: "Account" shows consolidated rows; "Bulk" expands each
partial leg / split-approval child into its own single-entry row. --}}
@if($selectedBankAccount)
Drag or place a transaction anywhere in this ledger. Moving onto another day updates its date automatically.
@endif
{{-- Transaction table --}}
@if($selectedBankAccount)
Order
@endif
Date
Type
Transaction ID
Transaction Type
Description
Amount
Cur
Balance
Actions
@forelse($transactions as $transaction)
@php
$txCurrency = $transactionCurrency($transaction);
// Direction is driven by the authoritative `type` column so a
// transfer's destination CREDIT leg renders green on the
// receiving account and red on the sending account.
$isIn = $transaction->type === 'income';
$txStatus = $transaction->metadata['status'] ?? 'Cleared';
$amtColor = $isIn ? '#10b981' : '#ef4444';
$invoiceNumber = $transaction->display_invoice_number ?? ($transaction->metadata['invoice_number'] ?? null);
$invoiceProductName = $transaction->display_invoice_product_name ?? ($transaction->metadata['invoice_product_name'] ?? null);
$transferPartialAmounts = collect($transaction->metadata['transfer_partial_amounts'] ?? [])
->filter(fn($amount) => is_numeric($amount) && (float) $amount > 0)
->values();
$transferPartialCurrency = $transaction->metadata['transfer_partial_currency'] ?? $txCurrency;
$approvalOriginalAmount = is_numeric($transaction->metadata['approval_original_amount'] ?? null) ? (float) $transaction->metadata['approval_original_amount'] : null;
$approvalApprovedAmount = is_numeric($transaction->metadata['approval_approved_amount'] ?? null) ? (float) $transaction->metadata['approval_approved_amount'] : null;
$approvalRemainingAmount = is_numeric($transaction->metadata['approval_remaining_amount'] ?? null) ? (float) $transaction->metadata['approval_remaining_amount'] : null;
$approvalExtraAmount = is_numeric($transaction->metadata['approval_extra_amount'] ?? null) ? (float) $transaction->metadata['approval_extra_amount'] : 0.0;
$hasApprovalDetail = $approvalOriginalAmount !== null && $approvalApprovedAmount !== null && $approvalRemainingAmount !== null;
$showApprovalRemaining = $txStatus === 'Pending' && $hasApprovalDetail;
$approvedPartialAmounts = collect($transaction->metadata['approval_breakdown'] ?? [])
->filter(fn($entry) => is_array($entry) && isset($entry['amount']) && is_numeric($entry['amount']) && (float) $entry['amount'] > 0)
->map(fn($entry) => (float) $entry['amount'])
->values();
if ($approvedPartialAmounts->isEmpty() && $showApprovalRemaining && (float) ($approvalApprovedAmount ?? 0) > 0) {
$approvedPartialAmounts = collect([(float) $approvalApprovedAmount]);
}
// Per-leg amounts of a collapsed split transfer, already in the
// account currency (USD), so each received installment shows its
// real dollar figure. Falls back to the approval breakdown for
// partial-approval rows that aren't split transfers.
$transferPartialDisplayAmounts = collect($transaction->metadata['transfer_partial_display_amounts'] ?? [])
->filter(fn($amount) => is_numeric($amount) && (float) $amount > 0)
->map(fn($amount) => (float) $amount)
->values();
$partialBreakdownAmounts = $transferPartialDisplayAmounts->isNotEmpty()
? $transferPartialDisplayAmounts
: $approvedPartialAmounts;
// Show what actually posted/was approved (the same figure the
// IN/OUT/NET summary uses): for a partially-approved pending
// transfer this is the approved-so-far amount, not the original
// total. The original + per-date breakdown remain in the popup.
$displayAmount = $postedDisplayAmount($transaction);
$amountDetailMessage = $amountDetailJson($transaction, $txCurrency, $approvalOriginalAmount, $approvalApprovedAmount, $approvalRemainingAmount, $transferPartialAmounts, $transferPartialCurrency);
$balanceTransactionId = (int) $transaction->id;
$canReorder = (bool) $selectedBankAccount;
$txDate = $transaction->transaction_date?->toDateString();
@endphp
@if($selectedBankAccount)
@if($canReorder && $txDate)
@else
-
@endif
@endif
{{ $transaction->transaction_date?->format('d M Y') }}
{{-- Credit/Debit direction, driven by the authoritative `type` column. --}}
{{ $isIn ? 'Credit' : 'Debit' }}
@if($txStatus === 'Pending')
Pending
@endif
{{ $transaction->transaction_id ?: '-' }}
{{-- Originating reference (Manual, KuickPay, Invoice, Transfer…), with
the invoice number underneath when the row is tied to one. --}}
{{ $transaction->referenceLabel() }}
@if($invoiceNumber)
@foreach($transferPartialAmounts as $partialAmount)
{{ $metrics->smartAmount($partialAmount) }}
@endforeach
@endif
@php
// A multi-day KuickPay deposit is one credit that settles
// several days; show each day's share inline so the single
// entry carries its full breakdown.
$kuickpayAllocations = collect($transaction->metadata['kuickpay_allocations'] ?? [])
->filter(fn($a) => is_array($a) && isset($a['amount']) && is_numeric($a['amount']))
->values();
@endphp
@if($txTab !== 'bulk' && $kuickpayAllocations->isNotEmpty())
@endif
{{-- Per-installment breakdown of the received amount, shown in the
account currency (USD). These are the real per-leg/approved
figures (e.g. 5,000.00 then 28.27), NOT an even split of the
PKR composition. Shown for already-posted rows; the Pending
block above handles in-flight approvals. --}}
@if($txTab !== 'bulk' && ! $showApprovalRemaining && $partialBreakdownAmounts->count() > 1)
@foreach($partialBreakdownAmounts as $partialBreakdownAmount)
{{-- View detail (eye) --}}
@php
$txLockReason = ($periodLockReasons ?? [])[$transaction->id] ?? null;
// "Close from this row": freezes the row's own account up to the
// row's date. Offered only on an unlocked, non-future row that
// actually belongs to an account we can close.
$txCloseDate = $transaction->transaction_date
? \Illuminate\Support\Carbon::parse($transaction->transaction_date)
: null;
$txCloseType = $transaction->bank_account_id ? 'bank' : ($transaction->credit_card_id ? 'credit_card' : null);
$txCloseId = $transaction->bank_account_id ?: $transaction->credit_card_id;
$txCanClose = ($periodClosingEnabled ?? false)
&& ! $txLockReason
&& $txCloseType !== null
&& $txCloseDate !== null
&& ! $txCloseDate->isFuture();
$txCloseAccountName = $transaction->bank_account_id
? ($selectedBankAccount->name ?? 'this account')
: 'this card';
@endphp
{{-- Edit (pencil) — greyed out once the period is closed; the
padlock that says why takes the tick's place below. --}}
@if($txLockReason)
@else
@endif
@if($txStatus === 'Pending')
@endif
{{-- Delete (trash) — disabled while the period is closed --}}
{{-- Close the period at this row --}}
@if($txCanClose)
@elseif($txLockReason)
{{-- Same slot as the tick: once the period is closed the
"close from here" action becomes the padlock that
explains why the row is frozen. Clicking it asks for
the closing password and unlocks this entry — plus the
matching entry on the other account for a transfer. --}}
@endif