--- /dev/null
+
+<div class="col-lg-4">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Barcode:</span>
+ </div>
+ <input type='text' id='barcode-search-input' class="form-control"
+ placeholder="Barcode" i18n-placeholder [ngModel]='barcode'/>
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary"
+ (click)="findUser()" i18n>Submit</button>
+ </div>
+ </div>
+</div>
+
+
--- /dev/null
+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);
+ }
+}
+
+
+++ /dev/null
-
-<eg-staff-banner bannerText="Search for Patron by Barcode" i18n-bannerText>
-</eg-staff-banner>
-
-<div class="col-lg-4">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Barcode:</span>
- </div>
- <input type='text' id='barcode-search-input' class="form-control"
- placeholder="Barcode" i18n-placeholder [ngModel]='barcode'/>
- <div class="input-group-append">
- <button class="btn btn-outline-secondary"
- (click)="findUser()" i18n>Submit</button>
- </div>
- </div>
-</div>
-
-
+++ /dev/null
-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);
- }
-}
-
-
+++ /dev/null
-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 {}
-
+++ /dev/null
-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 {}
--- /dev/null
+import {IdlObject} from '@eg/core/idl.service';
+
+export class PatronContext {
+
+ patron: IdlObject;
+
+ setPatron(patron: IdlObject) {
+ this.patron = patron;
+ }
+}
+
+
--- /dev/null
+
+.patron-content-pane {
+ margin-top: 10px;
+ position: fixed;
+ height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
--- /dev/null
+<eg-staff-banner bannerText="Manage Patrons" i18n-bannerText></eg-staff-banner>
+
+<div class="row">
+ <div class="col-lg-3">
+ <span *ngIf="context.patron" class="font-weight-bold" i18n>
+ {{patronService.namePart(context.patron, 'family_name')}},
+ {{patronService.namePart(context.patron, 'first_given_name')}}
+ {{patronService.namePart(context.patron, 'second_given_name')}}
+ </span>
+ </div>
+ <div class="col-lg-9">
+ <ul ngbNav #patronNav="ngbNav" class="nav-tabs"
+ [activeId]="patronTab" (navChange)="beforeTabChange($event)">
+ <li ngbNavItem="search">
+ <a ngbNavLink i18n>Patron Search</a>
+ <ng-template ngbNavContent>
+ <div class="patron-content-pane">
+ <eg-patron-search (patronsClicked)="patronsSelected($event)">
+ </eg-patron-search>
+ </div>
+ </ng-template>
+ </li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <ng-container *ngIf="showSummary">
+ <div class="col-lg-3">
+ <ng-container *ngIf="context.patron">
+ <eg-patron-summary [context]="context"></eg-patron-summary>
+ </ng-container>
+ </div>
+ <div class="col-lg-9">
+ <div [ngbNavOutlet]="patronNav"></div>
+ </div>
+ </ng-container>
+ <ng-container *ngIf="!showSummary">
+ <div class="col-lg-12">
+ <div [ngbNavOutlet]="patronNav"></div>
+ </div>
+ </ng-container>
+</div>
+
--- /dev/null
+import {Component, OnInit} 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 {PatronService} from '@eg/staff/share/patron/patron.service';
+import {PatronContext} from './patron-context';
+import {PatronSearchComponent} from '@eg/staff/share/patron/search.component';
+
+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'
+];
+
+@Component({
+ templateUrl: 'patrons.component.html',
+ styleUrls: ['patrons.component.css']
+})
+export class PatronsComponent implements OnInit {
+
+ context: PatronContext;
+ patronTab = 'search';
+ patronId: number;
+
+ showSummary = true;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private net: NetService,
+ private auth: AuthService,
+ public patronService: PatronService
+ ) {}
+
+ ngOnInit() {
+ this.route.paramMap.subscribe((params: ParamMap) => {
+ this.patronTab = params.get('tab') || 'search';
+ this.patronId = +params.get('id');
+ this.context = new PatronContext();
+
+ if (this.patronId) { this.loadPatron(); }
+ });
+ }
+
+ beforeTabChange(evt: NgbNavChangeEvent) {
+ // tab will change with route navigation.
+ evt.preventDefault();
+
+ this.patronTab = evt.nextId;
+ this.routeToTab();
+ }
+
+ routeToTab() {
+ const url =
+ `/staff/circ/patron/${this.patronId}/${this.patronTab}`;
+
+ this.router.navigate([url]);
+ }
+
+ patronsSelected(rows: any[]) {
+ if (rows.length === 1) {
+ const id = rows[0].id();
+ if (id !== this.patronId) {
+ this.patronId = id;
+ this.loadPatron();
+ return;
+ }
+ }
+ }
+
+ loadPatron() {
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.user.fleshed.retrieve',
+ this.auth.token(), this.patronId, PATRON_FLESH_FIELDS
+ ).subscribe(patron => this.context.setPatron(patron));
+ }
+}
+
--- /dev/null
+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 {PatronsComponent} from './patrons.component';
+import {SummaryComponent} from './summary.component';
+import {BcSearchComponent} from './bcsearch.component';
+
+@NgModule({
+ declarations: [
+ PatronsComponent,
+ SummaryComponent,
+ BcSearchComponent
+ ],
+ imports: [
+ StaffCommonModule,
+ FmRecordEditorModule,
+ HoldsModule,
+ HoldingsModule,
+ BookingModule,
+ PatronModule,
+ PatronRoutingModule
+ ],
+ providers: [
+ // PatronService
+ ]
+})
+
+export class PatronsModule {}
+
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
+import {PatronsComponent} from './patrons.component';
-const routes: Routes = [
- { path: 'bcsearch',
- loadChildren: () =>
- import('./bcsearch/bcsearch.module').then(m => m.BcSearchModule)
- }
-];
+const routes: Routes = [{
+ path: '',
+ pathMatch: 'full',
+ redirectTo: 'search'
+ }, {
+ path: 'search',
+ component: PatronsComponent
+ }, {
+ path: 'bcsearch',
+ component: PatronsComponent
+ }, {
+ path: ':id/:tab',
+ component: PatronsComponent,
+}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
-export class CircPatronRoutingModule {}
+export class PatronRoutingModule {}
--- /dev/null
+import {Component, OnInit, Input} 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 {PatronService} from '@eg/staff/share/patron/patron.service';
+import {PatronContext} from './patron-context';
+
+@Component({
+ templateUrl: 'summary.component.html',
+ selector: 'eg-patron-summary'
+})
+export class SummaryComponent implements OnInit {
+
+ @Input() context: PatronContext;
+
+ constructor(
+ private net: NetService,
+ public patronService: PatronService
+ ) {}
+
+ ngOnInit() {
+ }
+}
+
const routes: Routes = [
{ path: 'patron',
loadChildren: () =>
- import('./patron/routing.module').then(m => m.CircPatronRoutingModule)
+ import('./patron/patrons.module').then(m => m.PatronsModule)
}
];
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]();
+ }
}