--- /dev/null
+
+CHECKOUT
--- /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 {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() {
+ }
+}
+
+++ /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;
+ padding-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">
+ <h3 *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')}}
+ </h3>
+ </div>
+ <div class="col-lg-9">
+ <ul ngbNav #patronNav="ngbNav" class="nav-tabs"
+ [activeId]="patronTab" (navChange)="beforeTabChange($event)">
+
+ <li ngbNavItem="checkout">
+ <a ngbNavLink i18n>Checkout</a>
+ <ng-template ngbNavContent>
+ <div class="patron-content-pane">
+ <eg-patron-checkout></eg-patron-checkout>
+ </div>
+ </ng-template>
+ </li>
+
+ <li ngbNavItem="search">
+ <a ngbNavLink i18n>Patron Search</a>
+ <ng-template ngbNavContent>
+ <div class="patron-content-pane">
+ <eg-patron-search
+ (patronsClicked)="patronsClicked($event)"
+ (patronsSelected)="patronsSelected($event)">
+ </eg-patron-search>
+ </div>
+ </ng-template>
+ </li>
+
+ </ul>
+ </div>
+</div>
+
+<div class="row" *ngIf="viewInitDone">
+ <ng-container *ngIf="showSummary">
+ <div class="col-lg-3">
+ <ng-container *ngIf="context.patron">
+ <eg-patron-summary></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, 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 {PatronService} from '@eg/staff/share/patron/patron.service';
+import {PatronManagerService} from './patron.service';
+import {PatronSearchComponent} from '@eg/staff/share/patron/search.component';
+
+@Component({
+ templateUrl: 'patron.component.html',
+ styleUrls: ['patron.component.css']
+})
+export class PatronComponent implements OnInit, AfterViewInit {
+
+ patronTab = 'search';
+ patronId: number;
+ showSummary = true;
+ viewInitDone = false;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private net: NetService,
+ private auth: AuthService,
+ public patronService: PatronService,
+ public context: PatronManagerService
+ ) {}
+
+ ngOnInit() {
+ this.watchForTabChange();
+ }
+
+ watchForTabChange() {
+ this.route.paramMap.subscribe((params: ParamMap) => {
+ this.patronTab = params.get('tab') || 'search';
+ this.patronId = +params.get('id');
+
+ if (!this.patronId || (
+ this.context.patron &&
+ this.context.patron.id() === this.patronId)) {
+ return;
+ }
+
+ this.context.loadPatron(this.patronId);
+ });
+ }
+
+ // Trick that allows us to set a value for ngbNavOutlet, whose
+ // component is not avilable until after view init, without
+ // firing "expression changed after check" errors.
+ ngAfterViewInit() {
+ setTimeout(() => this.viewInitDone = true);
+ }
+
+ 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;
+ default:
+ url += `${this.patronId}/${this.patronTab}`;
+ }
+
+ this.router.navigate([url]);
+ }
+
+ // Patron row single-clicked in the grid. Load the patron without
+ // leaving the search tab.
+ patronsClicked(rows: any[]) {
+ if (rows.length !== 1) { return; }
+
+ const id = rows[0].id();
+ if (id !== this.patronId) {
+ this.patronId = id;
+ this.context.loadPatron(id);
+ return;
+ }
+ }
+
+ // Route to checkout tab for selected patron.
+ patronsSelected(rows: any[]) {
+ if (rows.length !== 1) { return; }
+
+ const id = rows[0].id();
+ this.patronId = id;
+ this.patronTab = 'checkout';
+ this.routeToTab();
+ }
+
+}
+
--- /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 {PatronManagerService} from './patron.service';
+import {PatronComponent} from './patron.component';
+import {SummaryComponent} from './summary.component';
+import {CheckoutComponent} from './checkout.component';
+import {BcSearchComponent} from './bcsearch.component';
+
+@NgModule({
+ declarations: [
+ PatronComponent,
+ SummaryComponent,
+ CheckoutComponent,
+ BcSearchComponent
+ ],
+ imports: [
+ StaffCommonModule,
+ FmRecordEditorModule,
+ HoldsModule,
+ HoldingsModule,
+ BookingModule,
+ PatronModule,
+ PatronRoutingModule
+ ],
+ providers: [
+ PatronManagerService
+ ]
+})
+
+export class PatronManagerModule {}
+
--- /dev/null
+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'
+];
+
+@Injectable()
+export class PatronManagerService {
+
+ patron: IdlObject;
+
+ constructor(
+ private net: NetService,
+ private auth: AuthService,
+ public patronService: PatronService
+ ) {}
+
+ loadPatron(id: number): Promise<IdlObject> {
+ 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);
+ }
+}
+
+
+++ /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';
+import {PatronComponent} from './patron.component';
const routes: Routes = [{
path: '',
redirectTo: 'search'
}, {
path: 'search',
- component: PatronsComponent
+ component: PatronComponent
}, {
path: 'bcsearch',
- component: PatronsComponent
+ component: PatronComponent
}, {
path: ':id/:tab',
- component: PatronsComponent,
+ component: PatronComponent,
}];
@NgModule({
--- /dev/null
+
+.patron-summary-container .row:nth-child(odd) {
+ background-color: rgb(248, 248, 248);
+}
+
+
-summary
+<div class="patron-summary-container">
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Profile</div>
+ <div class="col-lg-7">{{context.patron.profile().name()}}</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Home Library</div>
+ <div class="col-lg-7">{{orgSn(context.patron.home_ou())}}</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Net Access</div>
+ <div class="col-lg-7">{{context.patron.net_access_level().name()}}</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Date of Birth</div>
+ <div class="col-lg-7">{{context.patron.dob() | date:'shortDate'}}</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Parent/Guardian</div>
+ <div class="col-lg-7">{{context.patron.guardian()}}</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Last Activity</div>
+ <div class="col-lg-7">
+ <ng-container *ngIf="context.patron.usr_activity()[0]">
+ {{context.patron.usr_activity()[0].event_time() | date:'shortDate'}}
+ </ng-container>
+ </div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Last Updated</div>
+ <div class="col-lg-7">{{context.patron.last_update_time() | date:'shortDate'}}</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Create Date</div>
+ <div class="col-lg-7">{{context.patron.create_date() | date:'shortDate'}}</div>
+ </div>
+ <div class="row">
+ <div class="col-lg-5" i18n>Expire Date</div>
+ <div class="col-lg-7">{{context.patron.expire_date() | date:'shortDate'}}</div>
+ </div>
+
+ <hr class="m-1"/>
+
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Fines Owed</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Items Out</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Overdue</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Long Overdue</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Claimed Returned</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Lost</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Non-Cataloged</div>
+ <div class="col-lg-7">XXXX</div>
+ </div>
+ <div class="row">
+ <div class="col-lg-5" i18n>Holds</div>
+ <div class="col-lg-7">XX / YY</div>
+ </div>
+
+ <hr class="m-1"/>
+
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Card</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Username</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Day Phone</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Evening Phone</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Other Phone</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>ID1 </div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>ID2</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+ <div class="row mb-1">
+ <div class="col-lg-5" i18n>Email</div>
+ <div class="col-lg-7">XXXXXXXXXX</div>
+ </div>
+
+</div>
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 {PatronContext} from './patron-context';
+import {PatronManagerService} from './patron.service';
@Component({
templateUrl: 'summary.component.html',
+ styleUrls: ['summary-component.css'],
selector: 'eg-patron-summary'
})
export class SummaryComponent implements OnInit {
- @Input() context: PatronContext;
-
constructor(
+ private org: OrgService,
private net: NetService,
- public patronService: PatronService
+ public patronService: PatronService,
+ public context: PatronManagerService
) {}
ngOnInit() {
}
+
+ orgSn(orgId: number): string {
+ const org = this.org.get(orgId);
+ return org ? org.shortname() : '';
+ }
}
const routes: Routes = [
{ path: 'patron',
loadChildren: () =>
- import('./patron/patrons.module').then(m => m.PatronsModule)
+ import('./patron/patron.module').then(m => m.PatronManagerModule)
}
];