From: Bill Erickson Date: Wed, 12 Aug 2020 16:50:59 +0000 (-0400) Subject: LP1904036 Angular Patron UI initial structures X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=e61c285257d61057a6d1ffede0e54de7df2d6920;p=Evergreen.git LP1904036 Angular Patron UI initial structures Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch.component.html new file mode 100644 index 0000000000..db013a63e0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch.component.html @@ -0,0 +1,16 @@ + +
+
+
+ Barcode: +
+ +
+ +
+
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch.component.ts new file mode 100644 index 0000000000..ddd0433909 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch.component.ts @@ -0,0 +1,37 @@ +import {Component, OnInit, AfterViewInit} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; + +@Component({ + templateUrl: 'bcsearch.component.html', + selector: 'eg-patron-barcode-search' +}) + +export class BcSearchComponent implements OnInit, AfterViewInit { + + barcode = ''; + + constructor( + private route: ActivatedRoute, + private net: NetService, + private auth: AuthService + ) {} + + ngOnInit() { + this.barcode = this.route.snapshot.paramMap.get('barcode'); + if (this.barcode) { + this.findUser(); + } + } + + ngAfterViewInit() { + document.getElementById('barcode-search-input').focus(); + } + + findUser(): void { + alert('Searching for user ' + this.barcode); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html deleted file mode 100644 index e83cf9e9d8..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - -
-
-
- Barcode: -
- -
- -
-
-
- - diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts deleted file mode 100644 index dac5048eb7..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Component, OnInit, Renderer2} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; -import {NetService} from '@eg/core/net.service'; -import {AuthService} from '@eg/core/auth.service'; - -@Component({ - templateUrl: 'bcsearch.component.html' -}) - -export class BcSearchComponent implements OnInit { - - barcode = ''; - - constructor( - private route: ActivatedRoute, - private renderer: Renderer2, - private net: NetService, - private auth: AuthService - ) {} - - ngOnInit() { - - this.renderer.selectRootElement('#barcode-search-input').focus(); - this.barcode = this.route.snapshot.paramMap.get('barcode'); - - if (this.barcode) { - this.findUser(); - } - } - - findUser(): void { - alert('Searching for user ' + this.barcode); - } -} - - diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts deleted file mode 100644 index d1b16df384..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {NgModule} from '@angular/core'; -import {StaffCommonModule} from '@eg/staff/common.module'; -import {BcSearchRoutingModule} from './routing.module'; -import {BcSearchComponent} from './bcsearch.component'; - -@NgModule({ - declarations: [ - BcSearchComponent - ], - imports: [ - StaffCommonModule, - BcSearchRoutingModule, - ], -}) - -export class BcSearchModule {} - diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts deleted file mode 100644 index ce6783d4fd..0000000000 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {BcSearchComponent} from './bcsearch.component'; - -const routes: Routes = [ - { path: '', - component: BcSearchComponent - }, - { path: ':barcode', - component: BcSearchComponent - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) - -export class BcSearchRoutingModule {} diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.html new file mode 100644 index 0000000000..d6add2b645 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.html @@ -0,0 +1,2 @@ + +CHECKOUT diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.ts new file mode 100644 index 0000000000..e300bbe94e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/checkout.component.ts @@ -0,0 +1,25 @@ +import {Component, OnInit, Input} from '@angular/core'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {OrgService} from '@eg/core/org.service'; +import {NetService} from '@eg/core/net.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; +import {PatronManagerService} from './patron.service'; + +@Component({ + templateUrl: 'checkout.component.html', + selector: 'eg-patron-checkout' +}) +export class CheckoutComponent implements OnInit { + + constructor( + private org: OrgService, + private net: NetService, + public patronService: PatronService, + public context: PatronManagerService + ) {} + + ngOnInit() { + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html new file mode 100644 index 0000000000..6a1647b235 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html @@ -0,0 +1,7 @@ +
+
+ + + +
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts new file mode 100644 index 0000000000..c538ab7a8e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts @@ -0,0 +1,25 @@ +import {Component, OnInit, Input} from '@angular/core'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {OrgService} from '@eg/core/org.service'; +import {NetService} from '@eg/core/net.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; +import {PatronManagerService} from './patron.service'; + +@Component({ + templateUrl: 'edit-toolbar.component.html', + selector: 'eg-patron-edit-toolbar' +}) +export class EditToolbarComponent implements OnInit { + + constructor( + private org: OrgService, + private net: NetService, + public patronService: PatronService, + public context: PatronManagerService + ) {} + + ngOnInit() { + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html new file mode 100644 index 0000000000..139597f9cb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html @@ -0,0 +1,2 @@ + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts new file mode 100644 index 0000000000..61012290ac --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts @@ -0,0 +1,25 @@ +import {Component, OnInit, Input} from '@angular/core'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {OrgService} from '@eg/core/org.service'; +import {NetService} from '@eg/core/net.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; +import {PatronManagerService} from './patron.service'; + +@Component({ + templateUrl: 'edit.component.html', + selector: 'eg-patron-edit' +}) +export class EditComponent implements OnInit { + + constructor( + private org: OrgService, + private net: NetService, + public patronService: PatronService, + public context: PatronManagerService + ) {} + + ngOnInit() { + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.css b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.css new file mode 100644 index 0000000000..d4abd5f1e6 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.css @@ -0,0 +1,5 @@ + +::ng-deep legend { + font-size: 1rem; /* defaults to 1.5 */ +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html new file mode 100644 index 0000000000..55b619c1ea --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html @@ -0,0 +1,174 @@ + + + + + + + + + + + +
+ + +
+
+ + + +
+
+
+ +
+ + + +
+
+
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.ts new file mode 100644 index 0000000000..e65b8d2adc --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.ts @@ -0,0 +1,155 @@ +import {Component, OnInit, AfterViewInit} from '@angular/core'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; +import {PatronManagerService} from './patron.service'; +import {PatronSearchComponent} from '@eg/staff/share/patron/search.component'; + +const MAIN_TABS = + ['checkout', 'items_out', 'holds', 'bills', 'messages', 'edit', 'search']; + +@Component({ + templateUrl: 'patron.component.html', + styleUrls: ['patron.component.css'] +}) +export class PatronComponent implements OnInit, AfterViewInit { + + patronId: number; + patronTab = 'search'; + altTab: string; + showSummary = true; + loading = true; + + constructor( + private router: Router, + private route: ActivatedRoute, + private net: NetService, + private auth: AuthService, + private store: ServerStoreService, + public patronService: PatronService, + public context: PatronManagerService + ) {} + + ngOnInit() { + this.watchForTabChange(); + this.load(); + } + + load() { + this.loading = true; + this.fetchSettings() + .then(_ => this.loading = false); + } + + fetchSettings(): Promise { + + return this.store.getItemBatch([ + 'eg.circ.patron.summary.collapse' + ]).then(prefs => { + this.showSummary = !prefs['eg.circ.patron.summary.collapse']; + }); + } + + watchForTabChange() { + this.route.paramMap.subscribe((params: ParamMap) => { + this.patronTab = params.get('tab') || 'search'; + this.patronId = +params.get('id'); + + if (MAIN_TABS.includes(this.patronTab)) { + this.altTab = null; + } else { + this.altTab = this.patronTab; + this.patronTab = 'other'; + } + + const prevId = + this.context.patron ? this.context.patron.id() : null; + + if (this.patronId) { + if (this.patronId !== prevId) { // different patron + this.context.loadPatron(this.patronId); + } + } else { + // Use the ID of the previously loaded patron. + this.patronId = prevId; + } + }); + } + + ngAfterViewInit() { + } + + beforeTabChange(evt: NgbNavChangeEvent) { + // tab will change with route navigation. + evt.preventDefault(); + + this.patronTab = evt.nextId; + this.routeToTab(); + } + + routeToTab() { + let url = '/staff/circ/patron/'; + + switch (this.patronTab) { + case 'search': + case 'bcsearch': + url += this.patronTab; + break; + case 'other': + url += `${this.patronId}/${this.altTab}`; + break; + default: + url += `${this.patronId}/${this.patronTab}`; + } + + this.router.navigate([url]); + } + + showSummaryPane(): boolean { + return this.showSummary || this.patronTab === 'search'; + } + + toggleSummaryPane() { + this.store.setItem( // collapse is the opposite of show + 'eg.circ.patron.summary.collapse', this.showSummary); + this.showSummary = !this.showSummary; + } + + // Patron row single-clicked in the grid. Load the patron without + // leaving the search tab. + patronSelectionChange(ids: number[]) { + if (ids.length !== 1) { return; } + + const id = ids[0]; + if (id !== this.patronId) { + this.patronId = id; + this.context.loadPatron(id); + return; + } + } + + // Route to checkout tab for selected patron. + patronsActivated(rows: any[]) { + if (rows.length !== 1) { return; } + + const id = rows[0].id(); + this.patronId = id; + this.patronTab = 'checkout'; + this.routeToTab(); + } + + disablePurge(): boolean { + return + this.context.patron.super_user() === 't' || + this.patronId === this.auth.user().id(); + } + + purgeAccount() { + // show scary warning, etc. + + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts new file mode 100644 index 0000000000..76cd7dcf54 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts @@ -0,0 +1,41 @@ +import {NgModule} from '@angular/core'; +import {PatronRoutingModule} from './routing.module'; +import {FmRecordEditorModule} from '@eg/share/fm-editor/fm-editor.module'; +import {StaffCommonModule} from '@eg/staff/common.module'; +import {HoldsModule} from '@eg/staff/share/holds/holds.module'; +import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module'; +import {BookingModule} from '@eg/staff/share/booking/booking.module'; +import {PatronModule} from '@eg/staff/share/patron/patron.module'; +import {PatronManagerService} from './patron.service'; +import {PatronComponent} from './patron.component'; +import {SummaryComponent} from './summary.component'; +import {CheckoutComponent} from './checkout.component'; +import {EditComponent} from './edit.component'; +import {EditToolbarComponent} from './edit-toolbar.component'; +import {BcSearchComponent} from './bcsearch.component'; + +@NgModule({ + declarations: [ + PatronComponent, + SummaryComponent, + CheckoutComponent, + EditComponent, + EditToolbarComponent, + BcSearchComponent + ], + imports: [ + StaffCommonModule, + FmRecordEditorModule, + HoldsModule, + HoldingsModule, + BookingModule, + PatronModule, + PatronRoutingModule + ], + providers: [ + PatronManagerService + ] +}) + +export class PatronManagerModule {} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts new file mode 100644 index 0000000000..ef7c004acb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts @@ -0,0 +1,102 @@ +import {Injectable} from '@angular/core'; +import {IdlObject} from '@eg/core/idl.service'; +import {NetService} from '@eg/core/net.service'; +import {AuthService} from '@eg/core/auth.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; + +const PATRON_FLESH_FIELDS = [ + 'card', + 'cards', + 'settings', + 'standing_penalties', + 'addresses', + 'billing_address', + 'mailing_address', + 'stat_cat_entries', + 'waiver_entries', + 'usr_activity', + 'notes', + 'profile', + 'net_access_level', + 'ident_type', + 'ident_type2', + 'groups' +]; + +interface PatronStats { + fines: {balance_owed: number}; + checkouts: { + overdue: number, + claims_returned: number, + lost: number, + out: number, + total_out: number, + long_overdue: number + }; +} + +@Injectable() +export class PatronManagerService { + + patron: IdlObject; + patronStats: PatronStats; + + // Value for YAOUS circ.do_not_tally_claims_returned + noTallyClaimsReturned = false; + + // Value for YAOUS circ.tally_lost + tallyLost = false; + + loaded = false; + + constructor( + private net: NetService, + private auth: AuthService, + public patronService: PatronService + ) {} + + loadPatron(id: number): Promise { + this.loaded = false; + this.patron = null; + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.user.fleshed.retrieve', + this.auth.token(), id, PATRON_FLESH_FIELDS).toPromise() + .then(patron => this.patron = patron) + .then(_ => this.getPatronStats(id)) + .then(_ => this.loaded = true); + } + + getPatronStats(id: number): Promise { + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.user.opac.vital_stats.authoritative', + this.auth.token(), id).toPromise() + + .then((stats: PatronStats) => { + + // force numeric values + stats.fines.balance_owed = Number(stats.fines.balance_owed); + + Object.keys(stats.checkouts).forEach(key => + stats.checkouts[key] = Number(stats.checkouts[key])); + + stats.checkouts.total_out = stats.checkouts.out + + stats.checkouts.overdue + stats.checkouts.long_overdue + + if (!this.noTallyClaimsReturned) { + stats.checkouts.total_out += stats.checkouts.claims_returned; + } + + if (this.tallyLost) { + stats.checkouts.total_out += stats.checkouts.lost + } + + return this.patronStats = stats; + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts index a1b4ae680c..999d5f7779 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts @@ -1,20 +1,29 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; +import {PatronComponent} from './patron.component'; -const routes: Routes = [ - { path: 'bcsearch', - loadChildren: () => - import('./bcsearch/bcsearch.module').then(m => m.BcSearchModule) - }, - { path: 'event-log', +const routes: Routes = [{ + path: '', + pathMatch: 'full', + redirectTo: 'search' + }, { + path: 'event-log', loadChildren: () => import('./event-log/event-log.module').then(m => m.EventLogModule) - } -]; + }, { + path: 'search', + component: PatronComponent + }, { + path: 'bcsearch', + component: PatronComponent + }, { + path: ':id/:tab', + component: PatronComponent, +}]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class CircPatronRoutingModule {} +export class PatronRoutingModule {} diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.css b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.css new file mode 100644 index 0000000000..71ba8a412d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.css @@ -0,0 +1,6 @@ + +.patron-summary-container .row:nth-child(odd) { + background-color: rgb(248, 248, 248); +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html new file mode 100644 index 0000000000..d1d0eb7978 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html @@ -0,0 +1,141 @@ + +
+ +

+ {{patronService.namePart(context.patron, 'family_name')}}, + {{patronService.namePart(context.patron, 'first_given_name')}} + {{patronService.namePart(context.patron, 'second_given_name')}} +

+ +
+
Profile
+
{{context.patron.profile().name()}}
+
+
+
Home Library
+
{{orgSn(context.patron.home_ou())}}
+
+
+
Net Access
+
{{context.patron.net_access_level().name()}}
+
+
+
Date of Birth
+
{{context.patron.dob() | date:'shortDate'}}
+
+
+
Parent/Guardian
+
{{context.patron.guardian()}}
+
+
+
Last Activity
+
+ + {{context.patron.usr_activity()[0].event_time() | date:'shortDate'}} + +
+
+
+
Last Updated
+
{{context.patron.last_update_time() | date:'shortDate'}}
+
+
+
Create Date
+
{{context.patron.create_date() | date:'shortDate'}}
+
+
+
Expire Date
+
{{context.patron.expire_date() | date:'shortDate'}}
+
+ +
+ + + +
+
Fines Owed
+
{{context.patronStats.fines.total_owed | currency}}
+
+
+
Items Out
+
{{context.patronStats.checkouts.total_out}}
+
+
+
Overdue
+
{{context.patronStats.checkouts.overdue}}
+
+
+
Long Overdue
+
{{context.patronStats.checkouts.long_overdue}}
+
+
+
Claimed Returned
+
{{context.patronStats.checkouts.claims_returned}}
+
+
+
Lost
+
{{context.patronStats.checkouts.lost}}
+
+
+
Non-Cataloged
+
XXXX
+
+
+
Holds
+
XX / YY
+
+ +
+
+ +
+
Card
+
+ {{context.patron.card() ? context.patron.card().barcode() : ''}} +
+
+
+
Username
+
{{context.patron.usrname()}}
+
+
+
Day Phone
+
{{context.patron.day_phone()}}
+
+
+
Evening Phone
+
{{context.patron.evening_phone()}}
+
+
+
Other Phone
+
{{context.patron.other_phone()}}
+
+
+
ID1
+
{{context.patron.ident_value()}}
+
+
+
ID2
+
{{context.patron.ident_value2()}}
+
+
+
Email
+
+ + {{context.patron.email()}} +
+
+ +
+ +
+
+
+ {{addr.address_type()}} +
{{addr.street1()}} {{addr.street2()}}
+
{{addr.city()}}, {{addr.state()}} {{addr.post_code()}}
+
+ +
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts new file mode 100644 index 0000000000..90da1f9230 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts @@ -0,0 +1,31 @@ +import {Component, OnInit, Input} from '@angular/core'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {OrgService} from '@eg/core/org.service'; +import {NetService} from '@eg/core/net.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; +import {PatronManagerService} from './patron.service'; + +@Component({ + templateUrl: 'summary.component.html', + styleUrls: ['summary.component.css'], + selector: 'eg-patron-summary' +}) +export class SummaryComponent implements OnInit { + + constructor( + private org: OrgService, + private net: NetService, + public patronService: PatronService, + public context: PatronManagerService + ) {} + + ngOnInit() { + } + + orgSn(orgId: number): string { + const org = this.org.get(orgId); + return org ? org.shortname() : ''; + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts index 0e94cc5066..54e0ae4a99 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts @@ -4,7 +4,7 @@ import {RouterModule, Routes} from '@angular/router'; const routes: Routes = [{ path: 'patron', loadChildren: () => - import('./patron/routing.module').then(m => m.CircPatronRoutingModule) + import('./patron/patron.module').then(m => m.PatronManagerModule) }, { path: 'item', loadChildren: () => diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.html b/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.html index b25a4465dd..f52e36f12d 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.html @@ -37,7 +37,7 @@

Holds Count: {{holdsCount}}

-
+
Pickup Library
@@ -77,6 +77,12 @@ [initialValue]="enablePreFetch" i18n-label label="Pre-Fetch All Holds"> + + + + + @@ -141,6 +147,10 @@ i18-group group="Item" i18n-label label="Show in Catalog" (onClick)="showTitle($event)"> + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts index 7a27e5a8f4..6d70c327de 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts @@ -1,4 +1,5 @@ import {Component, OnInit, Input, ViewChild} from '@angular/core'; +import {Location} from '@angular/common'; import {Observable, Observer, of} from 'rxjs'; import {IdlObject} from '@eg/core/idl.service'; import {NetService} from '@eg/core/net.service'; @@ -102,13 +103,20 @@ export class HoldsGridComponent implements OnInit { } } - _userId: number; - @Input() set userId(id: number) { - this._userId = id; + get recordId(): number { + return this._recordId; + } + + _patronId: number; + @Input() set patronId(id: number) { + this._patronId = id; if (this.initDone) { this.holdsGrid.reload(); } } + get patronId(): number { + return this._patronId; + } // Include holds canceled on or after the provided date. // If no value is passed, canceled holds are not displayed. @@ -119,6 +127,9 @@ export class HoldsGridComponent implements OnInit { this.holdsGrid.reload(); } } + get showCanceledSince(): Date { + return this._showCanceledSince; + } // Include holds fulfilled on or after hte provided date. // If no value is passed, fulfilled holds are not displayed. @@ -129,6 +140,9 @@ export class HoldsGridComponent implements OnInit { this.holdsGrid.reload(); } } + get showFulfilledSince(): Date { + return this._showFulfilledSince; + } cellTextGenerator: GridCellTextGenerator; @@ -152,6 +166,7 @@ export class HoldsGridComponent implements OnInit { } constructor( + private ngLocation: Location, private net: NetService, private org: OrgService, private store: ServerStoreService, @@ -301,12 +316,12 @@ export class HoldsGridComponent implements OnInit { this.org.descendants(this.pickupLib, true); } - if (this._recordId) { - filters.record_id = this._recordId; + if (this.recordId) { + filters.record_id = this.recordId; } - if (this._userId) { - filters.usr_id = this._userId; + if (this.patronId) { + filters.usr_id = this.patronId; } return filters; @@ -315,7 +330,7 @@ export class HoldsGridComponent implements OnInit { fetchHolds(pager: Pager, sort: any[]): Observable { // We need at least one filter. - if (!this._recordId && !this.pickupLib && !this._userId && !this.pullListOrg) { + if (!this.recordId && !this.pickupLib && !this.patronId && !this.pullListOrg) { return of([]); } @@ -396,6 +411,15 @@ export class HoldsGridComponent implements OnInit { this.showDetail(rows[0]); } + showHoldsForTitle(rows: any[]) { + if (rows.length === 0) { return; } + + const url = this.ngLocation.prepareExternalUrl( + `/staff/catalog/record/${rows[0].record_id}/holds`); + + window.open(url, '_blank'); + } + showDetail(row: any) { if (row) { this.mode = 'detail'; diff --git a/Open-ILS/src/eg2/src/app/staff/share/patron/patron.service.ts b/Open-ILS/src/eg2/src/app/staff/share/patron/patron.service.ts index 32aa678e20..d459b45a10 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/patron/patron.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/patron/patron.service.ts @@ -50,5 +50,11 @@ export class PatronService { return this.pcrud.retrieve('au', id, pcrudOps).toPromise(); } + // Returns a name part (e.g. family_name) with preference for + // preferred name value where available. + namePart(patron: IdlObject, part: string): string { + if (!patron) { return ''; } + return patron['pref_' + part]() || patron[part](); + } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.html b/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.html index 952cb42558..fe8bcd824c 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.html @@ -180,8 +180,8 @@
diff --git a/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.ts b/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.ts index b2a4b5b316..1d35a766ac 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/patron/search.component.ts @@ -36,11 +36,13 @@ export class PatronSearchComponent implements OnInit, AfterViewInit { @ViewChild('searchGrid', {static: false}) searchGrid: GridComponent; - // Fired on dbl-click of a search result row. - @Output() patronsSelected: EventEmitter; + // Fires on dbl-click or Enter while one or more search result + // rows are selected. + @Output() patronsActivated: EventEmitter; - // Fired on single click of a search results row - @Output() patronsClicked: EventEmitter; + // Fires when the selection of search result rows changes. + // Emits an array of patron IDs + @Output() selectionChange: EventEmitter; search: any = {}; searchOrg: IdlObject; @@ -55,8 +57,8 @@ export class PatronSearchComponent implements OnInit, AfterViewInit { private auth: AuthService, private store: ServerStoreService ) { - this.patronsSelected = new EventEmitter(); - this.patronsClicked = new EventEmitter(); + this.patronsActivated = new EventEmitter(); + this.selectionChange = new EventEmitter(); this.dataSource = new GridDataSource(); this.dataSource.getRows = (pager: Pager, sort: any[]) => { return this.getRows(pager, sort); @@ -93,12 +95,12 @@ export class PatronSearchComponent implements OnInit, AfterViewInit { } } - rowsSelected(rows: IdlObject | IdlObject[]) { - this.patronsSelected.emit([].concat(rows)); + gridSelectionChange(keys: string[]) { + this.selectionChange.emit(keys.map(k => Number(k))); } - rowsClicked(rows: IdlObject | IdlObject[]) { - this.patronsClicked.emit([].concat(rows)); + rowsActivated(rows: IdlObject | IdlObject[]) { + this.patronsActivated.emit([].concat(rows)); } getSelected(): IdlObject[] { diff --git a/Open-ILS/src/eg2/src/styles.css b/Open-ILS/src/eg2/src/styles.css index 4e1366e870..08f6959cf6 100644 --- a/Open-ILS/src/eg2/src/styles.css +++ b/Open-ILS/src/eg2/src/styles.css @@ -163,6 +163,14 @@ a { font-size: 99%; } +/* Items stick to the top of the page once scrolled past, + * leaving room above for the nav bar */ +.sticky-top-with-nav { + top: 48px; + position: sticky; + z-index: 1; +} + /* -------------------------------------------------------------------------- /* Form Validation CSS - https://angular.io/guide/form-validation * TODO: these colors don't fit the EG color scheme diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.angular-patron.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.angular-patron.sql new file mode 100644 index 0000000000..c7b9bad124 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.angular-patron.sql @@ -0,0 +1,24 @@ + +BEGIN; + +-- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version); + +/* +INSERT INTO config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.catalog.results.count', 'gui', 'integer', + oils_i18n_gettext( + 'eg.catalog.results.count', + 'Catalog Results Page Size', + 'cwst', 'label' + ) +); +*/ + +eg.circ.patron.holds.prefetch + +eg.grid.circ.patron.holds + +holds_for_patron print template + +COMMIT;