LP#1938835: make the Angular staff portal/home page dynamic
authorGalen Charlton <gmc@equinoxOLI.org>
Mon, 2 Aug 2021 23:06:44 +0000 (19:06 -0400)
committerMike Rylander <mrylander@gmail.com>
Thu, 24 Mar 2022 12:55:01 +0000 (08:55 -0400)
Details and test plan will be in the commit with the release
notes.

Sponsored-by: Pioneer Library System
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/eg2/src/app/staff/splash.component.html
Open-ILS/src/eg2/src/app/staff/splash.component.ts
Open-ILS/src/eg2/src/app/staff/staff.module.ts

index 450a3e5..6f95510 100644 (file)
   </div>
 
   <div class="row" id="splash-nav">
-    <div class="col-lg-4">
+    <div class="col-lg-4" *ngFor="let header of portalHeaders; index as i">
       <div class="card">
-        <div class="card-header">
-          <h2 class="panel-title text-center" i18n>Circulation and Patrons</h2>
+        <div class="card-header" *ngIf="header">
+          <h2 class="panel-title text-center" i18n>{{header.label()}}</h2>
         </div>
         <div class="card-body">
           <div class="list-group">
-            <div class="list-group-item border-0 p-2">
-              <a href="/eg/staff/circ/patron/bcsearch" i18n>
-                <img src="/images/portal/forward.png" alt="" role="presentation"/>
-                Check Out Items
-              </a>
-            </div>
-            <div class="list-group-item border-0 p-2">
-              <a href="/eg/staff/circ/checkin/index" i18n>
-                <img src="/images/portal/back.png" alt="" role="presentation"/>
-                Check In Items
-              </a>
-            </div>
-            <div class="list-group-item border-0 p-2">
-              <a href="/eg/staff/circ/patron/search" i18n>
-                <img src="/images/portal/retreivepatron.png" alt="" role="presentation"/>
-                Search For Patron By Name
-              </a>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="col-lg-4">
-      <div class="card">
-        <div class="card-header">
-          <h2 class="panel-title text-center" i18n>Item Search and Cataloging</h2>
-        </div>
-        <div class="card-body">
-          <div class="list-group">
-            <div class="list-group-item border-0 p-2">
-              <div class="input-group">
-                <input type="text" class="form-control" 
-                  [(ngModel)]="catSearchQuery"
-                  id='catalog-search-input'
-                  (keyup.enter)="searchCatalog()"
-                  i18n-placeholder placeholder="Search for..."
-                  i18n-aria-label aria-label="Search for...">
-                <span class="input-group-btn">
-                  <button class="btn btn-outline-secondary" 
-                    (click)="searchCatalog()" type="button" i18n>
-                    Search Catalog
-                  </button>
-                </span>
-                  <!--
-                  <input focus-me="focus_search" 
-                      class="form-control" ng-model="cat_query" type="text" 
-                      ng-keypress="catalog_search($event)"
-                      placeholder="Search catalog for..."/>
-                  <button class='btn btn-light' ng-click="catalog_search()">
-                      Search
-                  </button>
-                  -->
+            <ng-container *ngFor="let entry of portalEntries[i]">
+              <div class="list-group-item border-0 p-2" *ngIf="entry.entry_type() === 'menuitem'">
+                <a href="{{entry.target_url()}}" i18n>
+                  <img src="{{entry.image_url()}}" alt="" role="presentation"/>
+                  {{entry.label()}}
+                </a>
               </div>
-            </div>
-            <div class="list-group-item border-0 p-2">
-              <a href="/eg/staff/cat/bucket/record/" i18n>
-                <img src="/images/portal/bucket.png" alt="" role="presentation"/>
-                Record Buckets
-              </a>
-            </div>
-            <div class="list-group-item border-0 p-2">
-              <a href="/eg/staff/cat/bucket/copy/" i18n>
-                <img src="/images/portal/bucket.png" alt="" role="presentation"/>
-                Item Buckets
-              </a>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="col-lg-4">
-      <div class="card">
-        <div class="card-header">
-          <h2 class="panel-title text-center" i18n>Administration</h2>
-        </div>
-        <div class="card-body">
-          <div class="list-group">
-            <div class="list-group-item border-0 p-2">
-              <a target="_top" href="http://docs.evergreen-ils.org/" i18n>
-                <img src="/images/portal/helpdesk.png" alt="" role="presentation"/>
-                Evergreen Documentation
-              </a>
-            </div>
-            <div class="list-group-item border-0 p-2">
-              <a target="_top" href="/eg/staff/admin/workstation/index" i18n>
-                <img src="/images/portal/helpdesk.png" alt="" role="presentation"/>
-                Workstation Administration
-              </a>
-            </div>
-            <div class="list-group-item border-0 p-2">
-              <a target="_top" href="/eg/staff/reporter/legacy/main" i18n>
-                <img src="/images/portal/reports.png" alt="" role="presentation"/>
-                Reports
-              </a>
-            </div>
+              <div class="list-group-item border-0 p-2" *ngIf="entry.entry_type() === 'catalogsearch'">
+                <div class="input-group">
+                  <input type="text" class="form-control" 
+                    [(ngModel)]="catSearchQuery"
+                    id='catalog-search-input'
+                    egAutofocus
+                    (keyup.enter)="searchCatalog()"
+                    i18n-placeholder placeholder="Search for..."
+                    i18n-aria-label aria-label="Search for...">
+                  <span class="input-group-btn">
+                    <button class="btn btn-outline-secondary" 
+                      (click)="searchCatalog()" type="button" i18n>
+                      {{entry.label()}}
+                    </button>
+                  </span>
+                </div>
+              </div>
+              <div class="list-group-item border-0 p-2" *ngIf="entry.entry_type() === 'link'">
+                <a target="_top" href="{{entry.target_url()}}" i18n>
+                  <img src="{{entry.image_url()}}" alt="" role="presentation"/>
+                  {{entry.label()}}
+                </a>
+              </div>
+              <div class="list-group-item border-0 p-2" *ngIf="entry.entry_type() === 'text'">
+                <h3 class="text-center" *ngIf="entry.label()" i18n>{{entry.label()}}</h3>
+                <div class="row">
+                  <div class="col-2" *ngIf="entry.image_url()">
+                    <img src="{{entry.image_url()}}" alt="" role="presentation"/>
+                  </div>
+                  <div class="col" [innerHtml]="entry.entry_text()"></div>
+                </div>
+              </div>
+            </ng-container>
           </div>
         </div>
       </div>
     </div>
   </div>
+
 </div>
 
index 6756b65..ccf9522 100644 (file)
@@ -1,4 +1,7 @@
-import {Component, OnInit, Renderer2} from '@angular/core';
+import {Component, OnInit, AfterViewInit, Directive, ElementRef, Renderer2} from '@angular/core';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
 import {Router} from '@angular/router';
 
 @Component({
@@ -8,16 +11,82 @@ import {Router} from '@angular/router';
 export class StaffSplashComponent implements OnInit {
 
     catSearchQuery: string;
+    portalEntries: any[][] = [];
+    portalHeaders: any[] = [];
 
     constructor(
         private renderer: Renderer2,
+        private pcrud: PcrudService,
+        private auth: AuthService,
+        private org: OrgService,
         private router: Router
     ) {}
 
     ngOnInit() {
+        const tmpPortalEntries: any[][] = [];
+        const wsAncestors = this.org.ancestors(this.auth.user().ws_ou(), true);
+        this.pcrud.search('cusppe', {owner: wsAncestors}).subscribe(
+            item => {
+                const page_col = item.page_col();
+                if (tmpPortalEntries[page_col] === undefined) {
+                    tmpPortalEntries[page_col] = [];
+                }
+                if (tmpPortalEntries[page_col][item.col_pos()] === undefined) {
+                    tmpPortalEntries[page_col][item.col_pos()] = [];
+                }
+                // we push here, then flatten the results when we filter
+                // by owner later because (page_col, col_pos) is not
+                // guaranteed to be unique
+                tmpPortalEntries[page_col][item.col_pos()].push(item);
+            },
+            err => {},
+            () => {
+                // find the first set of entries belonging to the
+                // workstation OU or one of its ancestors
+                let filteredPortalEntries: any[][] = [];
+                let foundMatch = false;
+                for (const ou of wsAncestors) {
+                    tmpPortalEntries.forEach((col) => {
+                        if (col !== undefined) {
+                            const filtered = col.reduce((prev, curr) => prev.concat(curr), [])
+                                                .filter(x => x !== undefined)
+                                                .filter(x => ou === x.owner());
+                            if (filtered.length) {
+                                foundMatch = true;
+                                filteredPortalEntries.push(filtered);
+                            }
+                        }
+                    });
+                    if (foundMatch) {
+                        break;
+                    } else {
+                        filteredPortalEntries = [];
+                    }
+                }
 
-        // Focus catalog search form
-        this.renderer.selectRootElement('#catalog-search-input').focus();
+                // munge the results so that we don't need to
+                // care if there are gaps in the page_col or col_pos
+                // sequences
+                filteredPortalEntries.forEach((col) => {
+                    if (col !== undefined) {
+                        const filtered = col.filter(x => x !== undefined);
+                        this.portalEntries.push(filtered);
+                        filtered.forEach((entry) => {
+                            if (entry.entry_type() === 'header') {
+                                this.portalHeaders[this.portalEntries.length - 1] = entry;
+                            }
+                        });
+                    }
+                });
+                // supply an empty header entry in case a column was
+                // defined without a header
+                this.portalEntries.forEach((col, i) => {
+                    if (this.portalHeaders.length <= i) {
+                        this.portalHeaders[i] = undefined;
+                    }
+                });
+            }
+        );
     }
 
     searchCatalog(): void {
@@ -30,4 +99,13 @@ export class StaffSplashComponent implements OnInit {
     }
 }
 
+@Directive({
+    selector: '[egAutofocus]'
+})
+export class AutofocusDirective implements AfterViewInit {
+    constructor(private host: ElementRef) {}
 
+    ngAfterViewInit() {
+        this.host.nativeElement.focus();
+    }
+}
index dd22f93..34bf589 100644 (file)
@@ -5,7 +5,7 @@ import {StaffComponent} from './staff.component';
 import {StaffRoutingModule} from './routing.module';
 import {StaffNavComponent} from './nav.component';
 import {StaffLoginComponent} from './login.component';
-import {StaffSplashComponent} from './splash.component';
+import {StaffSplashComponent, AutofocusDirective} from './splash.component';
 import {AboutComponent} from './about.component';
 
 @NgModule({
@@ -13,6 +13,7 @@ import {AboutComponent} from './about.component';
     StaffComponent,
     StaffNavComponent,
     StaffSplashComponent,
+    AutofocusDirective,
     StaffLoginComponent,
     AboutComponent
   ],