LPXXX Ang cat search filters / recents WIP user/berick/lpxxx-ang-catalog-search-templates
authorBill Erickson <berickxx@gmail.com>
Mon, 22 Jul 2019 21:50:01 +0000 (17:50 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 22 Jul 2019 21:50:01 +0000 (17:50 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/share/catalog/search-context.ts
Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts
Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts
Open-ILS/src/eg2/src/app/staff/catalog/search-filters.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/catalog/search-filters.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html
Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts
Open-ILS/src/sql/Pg/upgrade/XXXX.data.search-templates.sql [new file with mode: 0644]

index ef0fd55..565eca3 100644 (file)
@@ -483,5 +483,57 @@ export class CatalogSearchContext {
 
         return str;
     }
+
+    // A search context can collect enough data for multiple search
+    // types to be searchable (e.g. users navigate through parts of a
+    // search form).  Calling this method and providing a search type
+    // ensures the context is cleared of any data unrelated to the
+    // desired type.
+    scrub(searchType: string): void {
+
+        switch (searchType) {
+
+            case 'term': // AKA keyword search
+                this.marcSearch.reset();
+                this.browseSearch.reset();
+                this.identSearch.reset();
+                this.cnBrowseSearch.reset();
+                this.termSearch.hasBrowseEntry = '';
+                this.termSearch.browseEntry = null;
+                this.termSearch.fromMetarecord = null;
+                this.termSearch.facetFilters = [];
+                break;
+
+            case 'ident':
+                this.marcSearch.reset();
+                this.browseSearch.reset();
+                this.termSearch.reset();
+                this.cnBrowseSearch.reset();
+                break;
+
+            case 'marc':
+                this.browseSearch.reset();
+                this.termSearch.reset();
+                this.identSearch.reset();
+                this.cnBrowseSearch.reset();
+                break;
+
+            case 'browse':
+                this.marcSearch.reset();
+                this.termSearch.reset();
+                this.identSearch.reset();
+                this.cnBrowseSearch.reset();
+                this.browseSearch.pivot = null;
+                break;
+
+            case 'cnbrowse':
+                this.marcSearch.reset();
+                this.termSearch.reset();
+                this.identSearch.reset();
+                this.browseSearch.reset();
+                this.cnBrowseSearch.offset = 0;
+                break;
+        }
+    }
 }
 
index e78a951..5dae001 100644 (file)
@@ -26,6 +26,7 @@ import {HoldingsMaintenanceComponent} from './record/holdings.component';
 import {ConjoinedComponent} from './record/conjoined.component';
 import {CnBrowseComponent} from './cnbrowse.component';
 import {CnBrowseResultsComponent} from './cnbrowse/results.component';
+import {SearchFiltersComponent} from './search-filters.component';
 
 @NgModule({
   declarations: [
@@ -47,6 +48,7 @@ import {CnBrowseResultsComponent} from './cnbrowse/results.component';
     BrowseResultsComponent,
     ConjoinedComponent,
     HoldingsMaintenanceComponent,
+    SearchFiltersComponent,
     CnBrowseComponent,
     CnBrowseResultsComponent
   ],
index 1dac536..1b6dac1 100644 (file)
@@ -48,7 +48,8 @@ export class CatalogResolver implements Resolve<Promise<any[]>> {
             'cat.marcedit.stack_subfields',
             'cat.marcedit.flateditor',
             'cat.holdings_show_copies',
-            'cat.holdings_show_vols'
+            'cat.holdings_show_vols',
+            'opac.staff_saved_search.size'
         ]).then(settings => {
             this.staffCat.defaultSearchOrg =
                 this.org.get(settings['eg.search.search_lib']);
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-filters.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/search-filters.component.html
new file mode 100644 (file)
index 0000000..e3cdeb2
--- /dev/null
@@ -0,0 +1,62 @@
+
+<div class="d-flex justify-content-end">
+  
+  <ng-container *ngIf="recentSearchesCount > 0">
+    <div ngbDropdown>
+      <button class="btn btn-light" id="recentSearches"
+        ngbDropdownToggle i18n>Recent Searches</button>
+      <div ngbDropdownMenu aria-labelledby="recentSearches">
+        <a class="dropdown-item" routerLink="" i18n>
+          test
+        </a>
+      </div>
+    </div>
+  </ng-container>
+
+  <div ngbDropdown>
+    <button class="btn btn-light" id="searchFilters"
+      ngbDropdownToggle i18n>Search Filters</button>
+    <div ngbDropdownMenu aria-labelledby="searchFilters">
+      <a class="dropdown-item" i18n (click)="open()">
+        Save Search
+      </a>
+      <!-- TODO show template name in confirm dialog -->
+      <a class="dropdown-item" i18n>
+        Delete Selected
+      </a>
+      <div class="dropdown-divider"></div>
+      <!-- TODO HIGHLIGHT SELECTED TEMPLATE -->
+      <a *ngFor="let tmpl of templates"
+          class="dropdown-item" 
+          [ngClass]="{'font-weight-bold': tmpl.name === selectedTemplate}"
+          routerLink="/staff/catalog/search"
+          [queryParams]="tmpl.params">{{tmpl.name}}</a>
+    </div>
+  </div>
+</div>
+
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title" i18n>Save Template</h4>
+    <button type="button" class="close" 
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <div class="row">
+      <div class="col-lg-4" i18n id="templateNameLabel">Template Name:</div>
+      <div class="col-lg-6">
+        <input class="form-control" [(ngModel)]="templateName"
+          aria-labelledby="templateNameLabel"/>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" 
+      (click)="saveTemplate()" i18n>Save</button>
+    <button type="button" class="btn btn-warning" 
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
+
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-filters.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/search-filters.component.ts
new file mode 100644 (file)
index 0000000..181b7e3
--- /dev/null
@@ -0,0 +1,100 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {OrgService} from '@eg/core/org.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
+import {StaffCatalogService} from './catalog.service';
+import {AnonCacheService} from '@eg/share/util/anon-cache.service';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+
+const SAVED_TEMPLATES_SETTING = 'eg.catalog.search_templates';
+
+class SearchTemplate {
+    name: string;
+    params: any; // routerLink-compatible URL params object
+    constructor(name: string, params: any) {
+        this.name = name;
+        this.params = params;
+    }
+}
+
+@Component({
+  selector: 'eg-catalog-search-filters',
+  templateUrl: 'search-filters.component.html'
+})
+export class SearchFiltersComponent extends DialogComponent implements OnInit {
+
+    recentSearchesCount = 0;
+    context: CatalogSearchContext;
+    templates: SearchTemplate[] = [];
+    templateName: string;
+    selectedTemplate: string;
+
+    @Input() searchTab: string;
+
+    constructor(
+        private org: OrgService,
+        private store: ServerStoreService,  // search templates
+        private cache: AnonCacheService,    // recent searches
+        private cat: CatalogService,
+        private catUrl: CatalogUrlService,
+        private staffCat: StaffCatalogService,
+        private modal: NgbModal) {
+        super(modal);
+    }
+
+    ngOnInit() {
+        this.context = this.staffCat.searchContext;
+        this.org.settings('opac.staff_saved_search.size').then(sets => {
+            const size = sets['opac.staff_saved_search.size'];
+            if (size > 0) { this.recentSearchesCount = Number(size); }
+        });
+
+        this.getTemplates();
+    }
+
+    getTemplates(): Promise<any> {
+        this.templates = [];
+
+        return this.store.getItem(SAVED_TEMPLATES_SETTING).then(
+            templates => {
+                if (templates && templates.length) {
+                    this.templates = templates;
+
+                    // route index required to force the route to take
+                    // effect.  See ./catalog.service.ts
+                    this.templates.forEach(tmpl =>
+                        tmpl.params.ridx = this.staffCat.routeIndex++);
+                }
+            }
+        );
+    }
+
+    saveTemplate(): Promise<any> {
+        if (!this.templateName) { return; }
+
+        this.close();
+        const tmpContext = this.staffCat.cloneContext(this.context);
+        tmpContext.scrub(this.searchTab);
+        const urlParams = this.catUrl.toUrlParams(tmpContext);
+
+        // Some data should not go into the template.
+        delete urlParams.org;
+        delete urlParams.ridx;
+
+        // Clear the query values, but leave the query param in
+        // place since the CatalogUrl services expect to see one.
+        urlParams.query = [''];
+
+        this.templates.push(new SearchTemplate(this.templateName, urlParams));
+
+        return this.store.setItem(SAVED_TEMPLATES_SETTING, this.templates)
+        .then(_ => this.close());
+    }
+}
+
+
index 8d6e348..a80e76b 100644 (file)
@@ -345,6 +345,14 @@ TODO focus search input
         <eg-catalog-basket-actions></eg-catalog-basket-actions>
       </div>
     </div>
+    <div class="row mt-2">
+      <div class="col-lg-12">
+        <div class="float-right">
+          <eg-catalog-search-filters [searchTab]="searchTab">
+          </eg-catalog-search-filters>
+        </div>
+      </div>
+    </div>
   </div>
 </div>
 
index c8cee02..c3488df 100644 (file)
@@ -194,51 +194,21 @@ export class SearchFormComponent implements OnInit, AfterViewInit {
         // Form search overrides basket display
         this.context.showBasket = false;
 
-        switch (this.searchTab) {
+        this.context.scrub(this.searchTab);
 
-            case 'term': // AKA keyword search
-                this.context.marcSearch.reset();
-                this.context.browseSearch.reset();
-                this.context.identSearch.reset();
-                this.context.cnBrowseSearch.reset();
-                this.context.termSearch.hasBrowseEntry = '';
-                this.context.termSearch.browseEntry = null;
-                this.context.termSearch.fromMetarecord = null;
-                this.context.termSearch.facetFilters = [];
-                this.staffCat.search();
-                break;
+        switch (this.searchTab) {
 
+            case 'term':
             case 'ident':
-                this.context.marcSearch.reset();
-                this.context.browseSearch.reset();
-                this.context.termSearch.reset();
-                this.context.cnBrowseSearch.reset();
-                this.staffCat.search();
-                break;
-
             case 'marc':
-                this.context.browseSearch.reset();
-                this.context.termSearch.reset();
-                this.context.identSearch.reset();
-                this.context.cnBrowseSearch.reset();
                 this.staffCat.search();
                 break;
 
             case 'browse':
-                this.context.marcSearch.reset();
-                this.context.termSearch.reset();
-                this.context.identSearch.reset();
-                this.context.cnBrowseSearch.reset();
-                this.context.browseSearch.pivot = null;
                 this.staffCat.browse();
                 break;
 
             case 'cnbrowse':
-                this.context.marcSearch.reset();
-                this.context.termSearch.reset();
-                this.context.identSearch.reset();
-                this.context.browseSearch.reset();
-                this.context.cnBrowseSearch.offset = 0;
                 this.staffCat.cnBrowse();
                 break;
         }
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.search-templates.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.search-templates.sql
new file mode 100644 (file)
index 0000000..c68982e
--- /dev/null
@@ -0,0 +1,18 @@
+
+BEGIN;
+
+--SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label)
+VALUES (
+    -- hmm, we probably need a 'catalog' settings group
+    'eg.catalog.search_templates', 'gui', 'object',
+    oils_i18n_gettext(
+        'eg.catalog.search_templates',
+        'Staff Catalog Search Templates',
+        'cwst', 'label'
+    )
+);
+
+COMMIT;
+