feat(shares): move voucher search behind a popup, not an always-visible toolbar

The GL Account / Voucher No lookup is for the exception -- a receipt the
unattributed list and the member's own list do not already carry -- so it now
sits behind a "Search a Voucher Not Listed Below" button in both Apply
Collection and Apply Additional Shares, opening the same lookup in a popup
(matching the existing receipt-detail popup) instead of occupying screen space
whether or not anyone needs it. A successful find closes the popup and the
result appears ticked in the list behind it, same as before.
This commit is contained in:
2026-09-18 13:57:14 +05:30
parent 124f05d21d
commit dc7f668056
6 changed files with 294 additions and 101 deletions
@@ -178,61 +178,25 @@
<div class="funding-intro-banner">
<mat-icon class="banner-icon">info</mat-icon>
<span>
Select the one receipt that covers this payment, or enter a voucher number to search. Click
Select the one receipt that covers this payment. Click
<mat-icon class="inline-icon">visibility</mat-icon> on any row to view full details popup.
</span>
</div>
<!-- Toolbar: GL Account Select & Voucher Lookup -->
<div class="lookup-toolbar">
<div class="gl-select-box">
<label class="toolbar-label"> <mat-icon class="mini-icon">account_tree</mat-icon> GL Account </label>
<select
class="custom-select"
[(ngModel)]="glAccountNo"
[ngModelOptions]="{ standalone: true }"
[disabled]="lookingUpVoucher || submitting || loadingGlAccounts"
>
<option value="" disabled>
{{ loadingGlAccounts ? 'Loading GL accounts…' : 'Select GL account…' }}
</option>
@for (gl of glAccounts; track gl.id) {
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
}
</select>
</div>
<div class="voucher-lookup-box">
<label class="toolbar-label">
<mat-icon class="mini-icon">search</mat-icon> Find Specific Voucher
</label>
<div class="lookup-input-group">
<input
type="text"
class="lookup-input font-mono"
placeholder="Voucher no. (e.g. 000123)"
[(ngModel)]="voucherNo"
[disabled]="lookingUpVoucher || submitting"
(keyup.enter)="lookupVoucher()"
/>
<button
mat-flat-button
color="primary"
type="button"
class="find-btn"
[disabled]="!voucherNo.trim() || !glAccountNo.trim() || lookingUpVoucher || submitting"
(click)="lookupVoucher()"
>
@if (lookingUpVoucher) {
<mat-spinner diameter="14"></mat-spinner>
} @else {
<mat-icon>search</mat-icon>
<span>Find</span>
}
</button>
</div>
</div>
</div>
<!-- A voucher not already listed below (an older receipt, or one only the member's slip
names) is reached through the popup, not a toolbar sitting here whether or not
anyone needs it -- the unattributed list and this member's own receipts already
cover the ordinary case. -->
<button
mat-stroked-button
type="button"
class="voucher-search-toggle-btn"
[disabled]="submitting"
(click)="openVoucherSearch()"
>
<mat-icon>search</mat-icon>
<span>Search a Voucher Not Listed Below</span>
</button>
<!-- Receipts tagged "Share Collection" that belong to nobody yet. A deposit straight into
a GL account names no member, so it is never attributed on its own -- and never
@@ -649,3 +613,85 @@
</div>
</div>
}
<!-- SEARCH VOUCHER POPUP -->
@if (voucherSearchOpen) {
<div class="modal-backdrop animate-fade-in" (click)="closeVoucherSearch()">
<div class="voucher-modal-dialog voucher-search-dialog" (click)="$event.stopPropagation()">
<div class="modal-header">
<div class="header-title font-accent">
<mat-icon class="m-icon">search</mat-icon>
<span>Search a Voucher</span>
</div>
<button type="button" mat-icon-button class="close-btn" (click)="closeVoucherSearch()">
<mat-icon>close</mat-icon>
</button>
</div>
<div class="modal-body">
<p class="voucher-search-hint">
For a receipt not already listed above — one taken on an earlier day, or a slip the member is holding.
</p>
<div class="lookup-toolbar">
<div class="gl-select-box">
<label class="toolbar-label"><mat-icon class="mini-icon">account_tree</mat-icon> GL Account</label>
<select
class="custom-select"
[(ngModel)]="glAccountNo"
[ngModelOptions]="{ standalone: true }"
[disabled]="lookingUpVoucher || submitting || loadingGlAccounts"
>
<option value="" disabled>
{{ loadingGlAccounts ? 'Loading GL accounts…' : 'Select GL account…' }}
</option>
@for (gl of glAccounts; track gl.id) {
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
}
</select>
</div>
<div class="voucher-lookup-box">
<label class="toolbar-label"> <mat-icon class="mini-icon">receipt</mat-icon> Voucher / Receipt No </label>
<div class="lookup-input-group">
<input
type="text"
class="lookup-input font-mono"
placeholder="Voucher no. (e.g. 000123)"
[(ngModel)]="voucherNo"
[disabled]="lookingUpVoucher || submitting"
(keyup.enter)="lookupVoucher()"
/>
<button
mat-flat-button
color="primary"
type="button"
class="find-btn"
[disabled]="!voucherNo.trim() || !glAccountNo.trim() || lookingUpVoucher || submitting"
(click)="lookupVoucher()"
>
@if (lookingUpVoucher) {
<mat-spinner diameter="14"></mat-spinner>
} @else {
<mat-icon>search</mat-icon>
<span>Find</span>
}
</button>
</div>
</div>
</div>
@if (voucherError) {
<div class="alert-box alert-error">
<mat-icon>error_outline</mat-icon>
<span>{{ voucherError }}</span>
</div>
}
</div>
<div class="modal-footer">
<button type="button" mat-stroked-button (click)="closeVoucherSearch()">Close</button>
</div>
</div>
</div>
}
@@ -335,6 +335,36 @@ mat-form-field {
}
}
.voucher-search-toggle-btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.82rem;
font-weight: 600;
color: $accent-blue;
border-color: $primary-border;
margin-bottom: 1rem;
mat-icon {
font-size: 17px;
width: 17px;
height: 17px;
}
}
.voucher-search-hint {
font-size: 0.8rem;
color: $text-muted;
margin: 0 0 0.85rem;
}
// The shared .lookup-toolbar normally goes two-column above its own breakpoint of BROWSER width,
// which says nothing about how wide this popup itself is -- at the popup's width, two columns crowd
// the GL Account select against the voucher input. Single column reads better at this popup's width.
.voucher-search-dialog .lookup-toolbar {
grid-template-columns: 1fr;
}
/* Lookup Toolbar */
.lookup-toolbar {
display: grid;
@@ -100,6 +100,25 @@ export class ApplySharesComponent implements OnInit {
lookingUpVoucher = false;
voucherError = '';
/**
* Whether the voucher-search popup is open.
*
* The GL Account / Voucher No lookup is for the exception — a receipt the unattributed list and
* this member's own list do not carry — so it sits behind a button rather than on screen whether
* or not anyone needs it, exactly like the receipt-detail popup already does for the same reason.
*/
voucherSearchOpen = false;
openVoucherSearch(): void {
this.voucherError = '';
this.voucherSearchOpen = true;
}
closeVoucherSearch(): void {
this.voucherSearchOpen = false;
this.voucherError = '';
}
/** Voucher / Receipt No shown in the "Find Specific Voucher" box. Mirrors whichever receipt is
* ticked below (see {@link toggleVoucher}) so a tick is visibly confirmed up there too, and
* carries whatever the counter has typed in when they search by hand instead. */
@@ -445,6 +464,9 @@ export class ApplySharesComponent implements OnInit {
// Once attached it belongs to this member, so it leaves the unattributed list rather than
// sitting there inviting a second, conflicting attachment.
this.unattributed = this.unattributed.filter((u) => u.voucherNo !== voucherNo);
// Found it -- the popup has done its job, so it closes rather than sitting open over a
// receipt that now already shows, ticked, in the table behind it.
this.voucherSearchOpen = false;
});
}
@@ -80,57 +80,21 @@
</div>
}
<!-- SHARE RECEIPT LOOKUP SECTION -->
<!-- SHARE RECEIPT LOOKUP — a voucher not already sitting in the lists below (an older receipt,
or a member's own slip) is reached through the popup, not a toolbar that sat here whether or
not anyone needed it. The unattributed list and the member's own receipts already cover the
ordinary case; this is only for the exception. -->
<div class="search-section">
<div class="section-label">
<mat-icon class="sec-icon">search</mat-icon>
<span>Share Receipt Lookup</span>
</div>
<div class="lookup-toolbar">
<div class="gl-select-box">
<label class="toolbar-label"><mat-icon class="mini-icon">account_tree</mat-icon> GL Account</label>
<select
class="custom-select"
[(ngModel)]="glAccountNo"
[disabled]="lookingUp || submitting || loadingGlAccounts"
>
<option value="" disabled>{{ loadingGlAccounts ? 'Loading GL accounts…' : 'Select GL account…' }}</option>
@for (gl of glAccounts; track gl.id) {
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
}
</select>
</div>
<div class="voucher-lookup-box">
<label class="toolbar-label"><mat-icon class="mini-icon">receipt</mat-icon> Voucher / Receipt No</label>
<div class="lookup-input-group">
<input
type="text"
class="lookup-input font-mono"
placeholder="Voucher no. (e.g. CR/001/2608/0001)"
[(ngModel)]="voucherNo"
[disabled]="lookingUp || submitting"
(keyup.enter)="lookupVoucher()"
/>
<button
mat-flat-button
color="primary"
type="button"
class="find-btn"
[disabled]="!voucherNo.trim() || !glAccountNo.trim() || lookingUp || submitting"
(click)="lookupVoucher()"
>
@if (lookingUp) {
<mat-spinner diameter="14"></mat-spinner>
} @else {
<mat-icon>search</mat-icon>
<span>Find</span>
}
</button>
</div>
</div>
</div>
<button
mat-stroked-button
type="button"
class="voucher-search-toggle-btn"
[disabled]="submitting"
(click)="openVoucherSearch()"
>
<mat-icon>search</mat-icon>
<span>Search a Voucher Not Listed Below</span>
</button>
</div>
<!-- ENTRY FEE RECEIPT SECTION (If charge exists) -->
@@ -575,3 +539,82 @@
</div>
</div>
}
<!-- SEARCH VOUCHER POPUP -->
@if (voucherSearchOpen) {
<div class="modal-backdrop animate-fade-in" (click)="closeVoucherSearch()">
<div class="voucher-modal-dialog voucher-search-dialog" (click)="$event.stopPropagation()">
<div class="modal-header">
<div class="header-title font-accent">
<mat-icon class="m-icon">search</mat-icon>
<span>Search a Voucher</span>
</div>
<button type="button" mat-icon-button class="close-btn" (click)="closeVoucherSearch()">
<mat-icon>close</mat-icon>
</button>
</div>
<div class="modal-body">
<p class="voucher-search-hint">
For a receipt not already listed above — one taken on an earlier day, or a slip the member is holding.
</p>
<div class="lookup-toolbar">
<div class="gl-select-box">
<label class="toolbar-label"><mat-icon class="mini-icon">account_tree</mat-icon> GL Account</label>
<select
class="custom-select"
[(ngModel)]="glAccountNo"
[disabled]="lookingUp || submitting || loadingGlAccounts"
>
<option value="" disabled>{{ loadingGlAccounts ? 'Loading GL accounts…' : 'Select GL account…' }}</option>
@for (gl of glAccounts; track gl.id) {
<option [value]="gl.gl_code">{{ gl.gl_code }} — {{ gl.name }}</option>
}
</select>
</div>
<div class="voucher-lookup-box">
<label class="toolbar-label"><mat-icon class="mini-icon">receipt</mat-icon> Voucher / Receipt No</label>
<div class="lookup-input-group">
<input
type="text"
class="lookup-input font-mono"
placeholder="Voucher no. (e.g. CR/001/2608/0001)"
[(ngModel)]="voucherNo"
[disabled]="lookingUp || submitting"
(keyup.enter)="lookupVoucher()"
/>
<button
mat-flat-button
color="primary"
type="button"
class="find-btn"
[disabled]="!voucherNo.trim() || !glAccountNo.trim() || lookingUp || submitting"
(click)="lookupVoucher()"
>
@if (lookingUp) {
<mat-spinner diameter="14"></mat-spinner>
} @else {
<mat-icon>search</mat-icon>
<span>Find</span>
}
</button>
</div>
</div>
</div>
@if (errorMsg) {
<div class="alert-box alert-error">
<mat-icon>error_outline</mat-icon>
<span>{{ errorMsg }}</span>
</div>
}
</div>
<div class="modal-footer">
<button type="button" mat-stroked-button (click)="closeVoucherSearch()">Close</button>
</div>
</div>
</div>
}
@@ -253,6 +253,36 @@ $danger-border: #fecaca;
}
}
.voucher-search-toggle-btn {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 0.82rem;
font-weight: 600;
color: $accent-blue;
border-color: $primary-border;
mat-icon {
font-size: 17px;
width: 17px;
height: 17px;
}
}
.voucher-search-hint {
font-size: 0.8rem;
color: $text-muted;
margin: 0 0 0.85rem;
}
// The shared .lookup-toolbar normally goes two-column above 680px of BROWSER width, which says
// nothing about how wide this popup itself is -- at the popup's own ~36rem, two columns crowd the
// GL Account select against the voucher input. Single column reads better at this popup's width,
// full stop, rather than reintroducing a container query for one dialog.
.voucher-search-dialog .lookup-toolbar {
grid-template-columns: 1fr;
}
/* Lookup Toolbar - Wide Layout */
.lookup-toolbar {
display: grid;
@@ -72,6 +72,25 @@ export class ApplyCollectionDialogComponent implements OnInit {
voucherNo = '';
lookingUp = false;
/**
* Whether the voucher-search popup is open.
*
* The GL Account / Voucher No lookup is for the exception — a receipt the unattributed list and
* the member's own list do not carry — so it sits behind a button rather than on screen whether or
* not anyone needs it, exactly like the receipt-detail popup already does for the same reason.
*/
voucherSearchOpen = false;
openVoucherSearch(): void {
this.errorMsg = '';
this.voucherSearchOpen = true;
}
closeVoucherSearch(): void {
this.voucherSearchOpen = false;
this.errorMsg = '';
}
/** GL account the deposit was posted to — required, and always picked by hand. Never resolved
* from a share product's accounting setup, so a broken or missing product mapping can never
* make a genuine voucher unreachable. */
@@ -427,6 +446,9 @@ export class ApplyCollectionDialogComponent implements OnInit {
if (res.gl_code) {
this.glAccountNo = res.gl_code;
}
// Found it -- the popup has done its job, so it closes rather than sitting open over a
// receipt that now already shows, ticked, in the table behind it.
this.voucherSearchOpen = false;
});
}