LP1907286 Staff catalog sets last retrieved record
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / record / record.component.ts
1 import {Component, OnInit, Input, ViewChild, HostListener} from '@angular/core';
2 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
3 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
4 import {PcrudService} from '@eg/core/pcrud.service';
5 import {IdlObject} from '@eg/core/idl.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
8 import {CatalogService} from '@eg/share/catalog/catalog.service';
9 import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
10 import {StaffCatalogService} from '../catalog.service';
11 import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
12 import {StoreService} from '@eg/core/store.service';
13 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
14 import {MarcEditorComponent} from '@eg/staff/share/marc-edit/editor.component';
15 import {HoldingsMaintenanceComponent} from './holdings.component';
16 import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
17
18 @Component({
19   selector: 'eg-catalog-record',
20   templateUrl: 'record.component.html'
21 })
22 export class RecordComponent implements OnInit {
23
24     recordId: number;
25     recordTab: string;
26     summary: BibRecordSummary;
27     searchContext: CatalogSearchContext;
28     @ViewChild('recordTabs', { static: true }) recordTabs: NgbTabset;
29     @ViewChild('marcEditor', {static: false}) marcEditor: MarcEditorComponent;
30
31     @ViewChild('holdingsMaint', {static: false})
32         holdingsMaint: HoldingsMaintenanceComponent;
33
34     defaultTab: string; // eg.cat.default_record_tab
35
36     @ViewChild('pendingChangesDialog', {static: false})
37         pendingChangesDialog: ConfirmDialogComponent;
38
39     constructor(
40         private router: Router,
41         private route: ActivatedRoute,
42         private pcrud: PcrudService,
43         private auth: AuthService,
44         private bib: BibRecordService,
45         private cat: CatalogService,
46         private staffCat: StaffCatalogService,
47         private holdings: HoldingsService,
48         private store: StoreService
49     ) {}
50
51     ngOnInit() {
52         this.searchContext = this.staffCat.searchContext;
53
54         this.defaultTab =
55             this.store.getLocalItem('eg.cat.default_record_tab')
56             || 'item_table';
57
58         // Watch for URL record ID changes
59         // This includes the initial route.
60         // When applying the default configured tab, no navigation occurs
61         // to apply the tab name to the URL, it displays as the default.
62         // This is done so no intermediate redirect is required, which
63         // messes with browser back/forward navigation.
64         this.route.paramMap.subscribe((params: ParamMap) => {
65             this.recordTab = params.get('tab');
66             this.recordId = +params.get('id');
67             this.searchContext = this.staffCat.searchContext;
68
69             this.store.setLocalItem('eg.cat.last_record_retrieved', this.recordId);
70
71             if (!this.recordTab) {
72                 this.recordTab = this.defaultTab || 'item_table';
73             }
74
75             this.loadRecord();
76         });
77     }
78
79     setDefaultTab() {
80         this.defaultTab = this.recordTab;
81         this.store.setLocalItem('eg.cat.default_record_tab', this.recordTab);
82     }
83
84     // Changing a tab in the UI means changing the route.
85     // Changing the route ultimately results in changing the tab.
86     beforeTabChange(evt: NgbTabChangeEvent) {
87
88         // prevent tab changing until after route navigation
89         evt.preventDefault();
90
91         // Protect against tab changes with dirty data.
92         this.canDeactivate().then(ok => {
93             if (ok) {
94                 this.recordTab = evt.nextId;
95                 this.routeToTab();
96             }
97         });
98     }
99
100     /*
101      * Handle 3 types of navigation which can cause loss of data.
102      * 1. Record detail tab navigation (see also beforeTabChange())
103      * 2. Intra-Angular route navigation away from the record detail page
104      * 3. Browser page unload/reload
105      *
106      * For the #1, and #2, display a eg confirmation dialog.
107      * For #3 use the stock browser onbeforeunload dialog.
108      *
109      * Note in this case a tab change is a route change, but it's one
110      * which does not cause RecordComponent to unload, so it has to be
111      * manually tracked in beforeTabChange().
112      */
113     @HostListener('window:beforeunload', ['$event'])
114     canDeactivate($event?: Event): Promise<boolean> {
115
116         if (this.marcEditor && this.marcEditor.changesPending()) {
117
118             // Each warning dialog clears the current "changes are pending"
119             // flag so the user is not presented with the dialog again
120             // unless new changes are made.
121             this.marcEditor.clearPendingChanges();
122
123             if ($event) { // window.onbeforeunload
124                 $event.preventDefault();
125                 $event.returnValue = true;
126
127             } else { // tab OR route change.
128                 return this.pendingChangesDialog.open().toPromise();
129             }
130
131         } else {
132             return Promise.resolve(true);
133         }
134     }
135
136     routeToTab() {
137         const url =
138             `/staff/catalog/record/${this.recordId}/${this.recordTab}`;
139
140         // Retain search parameters
141         this.router.navigate([url], {queryParamsHandling: 'merge'});
142     }
143
144     loadRecord(): void {
145
146         // Avoid re-fetching the same record summary during tab navigation.
147         if (this.staffCat.currentDetailRecordSummary &&
148             this.recordId === this.staffCat.currentDetailRecordSummary.id) {
149             this.summary = this.staffCat.currentDetailRecordSummary;
150             return;
151         }
152
153         this.summary = null;
154         this.bib.getBibSummary(
155             this.recordId,
156             this.searchContext.searchOrg.id(),
157             this.searchContext.searchOrg.ou_type().depth()).toPromise()
158         .then(summary => {
159             this.summary =
160                 this.staffCat.currentDetailRecordSummary = summary;
161         });
162     }
163
164     // Lets us intercept the summary object and augment it with
165     // search highlight data if/when it becomes available from
166     // an externally executed search.
167     summaryForDisplay(): BibRecordSummary {
168         if (!this.summary) { return null; }
169         const sum = this.summary;
170         const ctx = this.searchContext;
171
172         if (Object.keys(sum.displayHighlights).length === 0) {
173             if (ctx.highlightData[sum.id]) {
174                 sum.displayHighlights = ctx.highlightData[sum.id];
175             }
176         }
177
178         return this.summary;
179     }
180
181     currentSearchOrg(): IdlObject {
182         if (this.staffCat && this.staffCat.searchContext) {
183             return this.staffCat.searchContext.searchOrg;
184         }
185         return null;
186     }
187
188     handleMarcRecordSaved() {
189         this.staffCat.currentDetailRecordSummary = null;
190         this.loadRecord();
191     }
192
193     // Our actions component broadcast a request to add holdings.
194     // If our Holdings Maintenance component is active/visible, ask
195     // it to figure out what data to pass to the holdings editor.
196     // Otherwise, just tell it to create a new call number and
197     // copy at the current working location.
198     addHoldingsRequested() {
199         if (this.holdingsMaint && this.holdingsMaint.holdingsGrid) {
200             this.holdingsMaint.openHoldingAdd(
201                 this.holdingsMaint.holdingsGrid.context.getSelectedRows(),
202                 true, true
203             );
204
205         } else {
206
207             this.holdings.spawnAddHoldingsUi(
208                 this.recordId, null, [{owner: this.auth.user().ws_ou()}]);
209         }
210     }
211 }
212
213