LP1849212: Improvements to course materials admin UI
authorJane Sandberg <sandbej@linnbenton.edu>
Mon, 10 Aug 2020 03:03:26 +0000 (20:03 -0700)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 22:17:18 +0000 (18:17 -0400)
- Better order for acmc fm-editor
- Add original item attributes to the course materials list
- trim whitespace from barcodes
- accessible labels in the course page
- refactor course users
- fix incorrect paths in course materials grid columns

Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>

14 files changed:
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-material.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-material.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-page.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-page.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/routing.module.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts
Open-ILS/src/eg2/src/app/staff/common.module.ts
Open-ILS/src/eg2/src/app/staff/share/course.service.ts
Open-ILS/src/eg2/src/app/staff/share/marc-edit/marcrecord.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Courses.pm

index 9acbe99..73e5bdd 100644 (file)
@@ -6,7 +6,7 @@
 </eg-string>
 <eg-string #materialEditSuccessString i18n-text text="Update of Course Material succeeded"></eg-string>
 <eg-string #materialEditFailedString i18n-text text="Update of Course Material failed or was not allowed"></eg-string>
-<eg-string #MaterialAddDifferentLibraryString i18n-text text="Material exists at a different library"></eg-string>
+<eg-string #materialAddDifferentLibraryString i18n-text text="Material exists at a different library"></eg-string>
 
 <ng-template #dialogContent>
   <div class="modal-header bg-info" [ngClass]="isDialog() ? 'modal-header' : 'alert mt-3'">
                 <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
                   <div class="input-group">
                     <div class="input-group-prepend">
-                      <span class="input-group-text" i18n>Barcode</span>
+                      <label for="associate-item-barcode" class="input-group-text" i18n>Barcode</label>
                     </div>
-                    <input type="text" class="flex-grow-1" [(ngModel)]="barcodeInput" (click)="$event.target.select()"
+                    <input type="text" class="flex-grow-1" id="associate-item-barcode"
+                      [(ngModel)]="barcodeInput" (click)="$event.target.select()"
                       [disabled]="currentCourse && currentCourse.is_archived() == 't'"
                       (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
                   </div>
                   <div class="input-group">
                     <div class="input-group-prepend">
                       <div class="input-group-text">
-                        <span i18n>Call Number</span>
+                        <label for="associate-item-temp-call-number" i18n>Call Number</label>
                       </div>
                     </div>
-                    <input type="text" [(ngModel)]="tempCallNumber"
+                    <input type="text" [(ngModel)]="tempCallNumber" label="associate-item-temp-call-number"
                       [disabled]="currentCourse && currentCourse.is_archived() == 't'"
                       (input)="isModifyingCallNumber = true" class="flex-grow-1" />
                     <div class="input-group-append">
           <eg-grid-column path="id" [index]=true [hidden]="true" label="ID" i18n-label></eg-grid-column>
           <eg-grid-column label="Barcode" i18n-label name="barcode" [cellTemplate]="barcodeCellTemplate"></eg-grid-column>
           <eg-grid-column label="Title" i18n-label name="title" flex="3" [cellTemplate]="titleCellTemplate"></eg-grid-column>
-          <eg-grid-column path="call_number.label" label="Call Number" i18n-label></eg-grid-column>
-          <eg-grid-column path="call_number.prefix.label" [hidden]="true" label="Call Number Prefix" i18n-label hidden>
+          <eg-grid-column path="item.call_number.label" label="Call Number" i18n-label></eg-grid-column>
+          <eg-grid-column path="item.call_number.prefix.label" [hidden]="true" label="Call Number Prefix" i18n-label hidden>
           </eg-grid-column>
-          <eg-grid-column path="call_number.suffix.label" [hidden]="true" label="Call Number Suffix" i18n-label hidden>
+          <eg-grid-column path="item.call_number.suffix.label" [hidden]="true" label="Call Number Suffix" i18n-label hidden>
           </eg-grid-column>
-          <eg-grid-column path="circ_modifier" [hidden]="true" label="Circulation Modifier" i18n-label></eg-grid-column>
-          <eg-grid-column path="circ_lib.shortname" label="Circulation Library" i18n-label></eg-grid-column>
-          <eg-grid-column path="location.name" [hidden]="true" label="Shelving Location" i18n-label></eg-grid-column>
-          <eg-grid-column path="status.name" [hidden]="true" label="Copy Status" i18n-label></eg-grid-column>
+          <eg-grid-column path="item.circ_modifier" [hidden]="true" label="Circulation Modifier" i18n-label></eg-grid-column>
+          <eg-grid-column path="item.circ_lib.shortname" label="Circulation Library" i18n-label></eg-grid-column>
+          <eg-grid-column path="item.location.name" [hidden]="true" label="Shelving Location" i18n-label></eg-grid-column>
+          <eg-grid-column path="item.status.name" [hidden]="true" label="Item Status" i18n-label></eg-grid-column>
+          <eg-grid-column path="original_circ_modifier.name" [hidden]="true" label="Original Circulation Modifier" i18n-label></eg-grid-column>
+          <eg-grid-column path="original_location.name" [hidden]="true" label="Original Shelving Location" i18n-label></eg-grid-column>
+          <eg-grid-column path="original_status.name" [hidden]="true" label="Original Item Status" i18n-label></eg-grid-column>
           <eg-grid-column path="relationship" label="Relationship" i18n-label></eg-grid-column>
         </eg-grid>
       </div>
 
 <eg-fm-record-editor #editDialog idlClass='acmcm' [fieldOptions]="{course: {linkedSearchField: 'course_number'}}"
   [preloadLinkedValues]="true"
-  hiddenFields="id,item,original_callnumber,original_status,original_location,original_circ_modifier,record">
+  hiddenFields="id,item,original_callnumber,original_status,original_location,original_circ_modifier,record,temporary_record">
 </eg-fm-record-editor>
index 541ea1a..bb69519 100644 (file)
@@ -132,9 +132,9 @@ export class CourseAssociateMaterialComponent extends DialogComponent implements
         editOneThing(itemFields.shift());
     }
 
-    showEditDialog(course_material: IdlObject): Promise<any> {
+    showEditDialog(courseMaterial: IdlObject): Promise<any> {
         this.editDialog.mode = 'update';
-        this.editDialog.recordId = course_material._id;
+        this.editDialog.recordId = courseMaterial.id();
         return new Promise((resolve, reject) => {
             this.editDialog.open({size: 'lg'}).subscribe(
                 result => {
@@ -142,11 +142,9 @@ export class CourseAssociateMaterialComponent extends DialogComponent implements
                         .then(str => this.toast.success(str));
                     this.pcrud.retrieve('acmcm', result).subscribe(material => {
                         if (material.course() !== this.courseId) {
-                            this.materialsDataSource.data.splice(
-                                this.materialsDataSource.data.indexOf(course_material, 0), 1
-                            );
+                            this.materialsGrid.reload();
                         } else {
-                            course_material._relationship = material.relationship();
+                            courseMaterial.relationship = material.relationship();
                         }
                     });
                     resolve(result);
@@ -163,7 +161,7 @@ export class CourseAssociateMaterialComponent extends DialogComponent implements
     associateItem(barcode, relationship) {
         if (barcode) {
             const args = {
-                barcode: barcode,
+                barcode: barcode.trim(),
                 relationship: relationship,
                 isModifyingCallNumber: this.isModifyingCallNumber,
                 isModifyingCircMod: this.isModifyingCircMod,
index a0969bf..f96087d 100644 (file)
         <div class="d-flex"  [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
           <div class="input-group">
             <div class="input-group-prepend">
-              <span class="input-group-text" i18n>Patron Barcode</span>
+              <label for="associate-user-barcode" class="input-group-text" i18n>Patron Barcode</label>
             </div>
-            <input type="text" class="flex-grow-1" [(ngModel)]="userBarcode"
-              (click)="$event.target.select()" 
+            <input type="text" class="flex-grow-1" id="associate-user-barcode"
+              [(ngModel)]="userBarcode" (click)="$event.target.select()"
               [disabled]="currentCourse && currentCourse.is_archived() == 't'"
               (keyup.enter)="associateUser(userBarcode)" />
           </div>
@@ -34,9 +34,9 @@
         <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
           <div class="input-group">
             <div class="input-group-prepend">
-              <span class="input-group-text" i18n>Role</span>
+              <label for="associate-user-role" class="input-group-text" i18n>Role</label>
             </div>
-            <input type="text" [(ngModel)]="userRoleInput"
+            <input type="text" [(ngModel)]="userRoleInput" id="associate-user-role"
               [disabled]="currentCourse && currentCourse.is_archived() == 't'"
               placeholder-i18n placeholder="e.g. Student, TA, Instructor..."
               class="flex-grow-1" />
           <div class="input-group">
             <div class="input-group-prepend">
               <div class="input-group-text">
-                <span i18n>Is Public Role?</span>
+                <label for="associate-user-public" i18n>Is Public Role?</label>
               </div>
             </div>
             <div class="input-group-append">
               <div class="input-group-text">
-                <input type="checkbox" [(ngModel)]="isPublicRole"
+                <input type="checkbox" [(ngModel)]="isPublicRole" id="associate-user-public"
                   [disabled]="currentCourse && currentCourse.is_archived() == 't'"
                   aria-label="Checkbox for allowing user to display on the OPAC Course Page" />
               </div>
       </div>
     </div>
     <div class="mt-3" [ngClass]="isDialog() ? 'col-md-12' : 'col-md-8'">
-      <eg-grid #usersGrid [dataSource]="usersDataSource">
+      <eg-grid #usersGrid [dataSource]="usersDataSource" [useLocalSort]="true">
         <eg-grid-toolbar-action label="Remove Selected" i18n-label (onClick)="deleteSelectedUsers($event)">
         </eg-grid-toolbar-action>
         <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelectedUsers($event)">
         </eg-grid-toolbar-action>
-        <eg-grid-column label="ID" path="_id" [index]=true [hidden]="true" i18n-label></eg-grid-column>
-        <eg-grid-column label="First Name" name="first_given_name" i18n-label></eg-grid-column>
-        <eg-grid-column label="Second Name" name="second_given_name" i18n-label></eg-grid-column>
-        <eg-grid-column label="Last Name" name="family_name" i18n-label></eg-grid-column>
-        <eg-grid-column label="Prefix" name="pref_prefix" [hidden]="true" i18n-label></eg-grid-column>
-        <eg-grid-column label="Preferred First Name" name="pref_first_given_name"[hidden]="true"  i18n-label></eg-grid-column>
-        <eg-grid-column label="Preferred Second Name" name="pref_second_given_name"[hidden]="true"  i18n-label></eg-grid-column>
-        <eg-grid-column label="Preferred Family Name" name="pref_family_name"[hidden]="true"  i18n-label></eg-grid-column>
-        <eg-grid-column label="Preferred Suffix" name="pref_suffix" [hidden]="true" i18n-label></eg-grid-column>
-        <eg-grid-column label="User Role" name="_role" i18n-label></eg-grid-column>
-        <eg-grid-column label="Viewable on OPAC" name="_is_public" i18n-label datatype="bool"></eg-grid-column>
+        <eg-grid-column label="User ID" path="usr.id" [index]=true [hidden]="true" i18n-label></eg-grid-column>
+        <eg-grid-column label="First Name" path="usr.first_given_name" i18n-label></eg-grid-column>
+        <eg-grid-column label="Second Name" path="usr.second_given_name" i18n-label></eg-grid-column>
+        <eg-grid-column label="Last Name" path="usr.family_name" i18n-label></eg-grid-column>
+        <eg-grid-column label="Prefix" path="usr.pref_prefix" [hidden]="true" i18n-label></eg-grid-column>
+        <eg-grid-column label="Preferred First Name" path="usr.pref_first_given_name"[hidden]="true"  i18n-label></eg-grid-column>
+        <eg-grid-column label="Preferred Second Name" path="usr.pref_second_given_name"[hidden]="true"  i18n-label></eg-grid-column>
+        <eg-grid-column label="Preferred Family Name" path="usr.pref_family_name"[hidden]="true"  i18n-label></eg-grid-column>
+        <eg-grid-column label="Preferred Suffix" path="usr.pref_suffix" [hidden]="true" i18n-label></eg-grid-column>
+        <eg-grid-column label="User Role" path="usr_role" i18n-label></eg-grid-column>
+        <eg-grid-column label="Viewable on OPAC" path="is_public" i18n-label datatype="bool"></eg-grid-column>
       </eg-grid>
     </div>
   </div>
   [fieldOptions]="{course: {linkedSearchField: 'course_number'}}"
   [preloadLinkedValues]="true"
   hiddenFields="id,usr">
-</eg-fm-record-editor>
\ No newline at end of file
+</eg-fm-record-editor>
index 1b1ebfa..d9360cc 100644 (file)
@@ -1,19 +1,14 @@
-import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';
-import {Router, ActivatedRoute} from '@angular/router';
-import {Observable, Observer, of} from 'rxjs';
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
 import {DialogComponent} from '@eg/share/dialog/dialog.component';
 import {AuthService} from '@eg/core/auth.service';
 import {NetService} from '@eg/core/net.service';
-import {EventService} from '@eg/core/event.service';
-import {OrgService} from '@eg/core/org.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {Pager} from '@eg/share/util/pager';
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
 import {GridDataSource} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
 import {IdlObject, IdlService} from '@eg/core/idl.service';
 import {StringComponent} from '@eg/share/string/string.component';
-import {StaffBannerComponent} from '@eg/staff/share/staff-banner.component';
 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
 import {ToastService} from '@eg/share/toast/toast.service';
 import {CourseService} from '@eg/staff/share/course.service';
@@ -25,11 +20,11 @@ import {CourseService} from '@eg/staff/share/course.service';
 
 export class CourseAssociateUsersComponent extends DialogComponent implements OnInit {
     @Input() currentCourse: IdlObject;
-    @Input() courseId: any;
+    @Input() courseId: number;
     @Input() displayMode: String;
     users: any[] = [];
     @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
-    @ViewChild('usersGrid', {static: true}) usersGrid: GridComponent;
+    @ViewChild('usersGrid') usersGrid: GridComponent;
     @ViewChild('userDeleteFailedString', { static: true })
         userDeleteFailedString: StringComponent;
     @ViewChild('userDeleteSuccessString', { static: true })
@@ -43,19 +38,15 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
     @ViewChild('userEditFailedString', { static: true })
         userEditFailedString: StringComponent;
     usersDataSource: GridDataSource;
-    @Input() userBarcode: String;
-    @Input() userRoleInput: String;
-    @Input() isPublicRole: Boolean;
+    userBarcode: String;
+    userRoleInput: String;
+    isPublicRole: Boolean;
 
     constructor(
         private auth: AuthService,
         private course: CourseService,
-        private event: EventService,
-        private idl: IdlService,
         private net: NetService,
-        private org: OrgService,
         private pcrud: PcrudService,
-        private route: ActivatedRoute,
         private toast: ToastService,
         private modal: NgbModal
     ) {
@@ -65,52 +56,36 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
 
     ngOnInit() {
         this.usersDataSource.getRows = (pager: Pager, sort: any[]) => {
-            return this.loadUsersGrid(pager);
-        }
+            return this.course.getUsers([this.courseId]);
+        };
     }
 
     isDialog(): boolean {
         return this.displayMode === 'dialog';
     }
 
-    loadUsersGrid(pager: Pager): Observable<any> {
-        return new Observable<any>(observer => {
-            this.course.getUsers(this.courseId).then(users => {
-                users.forEach(user => {
-                    this.course.fleshUser(user).then(fleshed_user => {
-                        this.usersDataSource.data.push(fleshed_user);
-                    });
-                    observer.complete();
-                });
-            });
-        });
-    }
-
     associateUser(barcode) {
         if (barcode) {
-            let args = {
+            const args = {
                 currentCourse: this.currentCourse,
-                barcode: barcode,
+                barcode: barcode.trim(),
                 role: this.userRoleInput,
                 is_public: this.isPublicRole
-            }
+            };
 
             this.userBarcode = null;
 
             this.net.request(
                 'open-ils.actor',
                 'open-ils.actor.user.retrieve_id_by_barcode_or_username',
-                this.auth.token(), barcode
+                this.auth.token(), barcode.trim()
             ).subscribe(patron => {
-                let associatedUser = this.course.associateUsers(patron, args).then(res => {
-                    this.course.fleshUser(res).then(fleshed_user => {
-                        this.usersDataSource.data.push(fleshed_user);
-                        this.userAddSuccessString.current().then(str => this.toast.success(str));
-                    });
+                    this.course.associateUsers(patron, args)
+                    .then(() => this.usersGrid.reload());
                 }, err => {
                     this.userAddFailedString.current().then(str => this.toast.danger(str));
-                });
-            });
+                }
+            );
         }
     }
 
@@ -134,14 +109,7 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
                 result => {
                     this.userEditSuccessString.current()
                         .then(str => this.toast.success(str));
-                    this.pcrud.retrieve('acmcu', result).subscribe(u => {
-                        if (u.course() != this.courseId) {
-                            this.usersDataSource.data.splice(this.usersDataSource.data.indexOf(user, 0), 1);
-                        } else {
-                            user._is_public = u.is_public();
-                            user._role = u.usr_role();
-                        }
-                    });
+                    this.usersGrid.reload();
                     resolve(result);
                 },
                 error => {
@@ -154,17 +122,14 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
     }
 
     deleteSelectedUsers(users) {
-        let user_ids = [];
-        users.forEach(user => {
-            this.usersDataSource.data.splice(this.usersDataSource.data.indexOf(user, 0), 1);
-            user_ids.push(user.id())
-        });
+        const user_ids = [];
         this.pcrud.search('acmcu', {course: this.courseId, usr: user_ids}).subscribe(user => {
             user.isdeleted(true);
             this.pcrud.autoApply(user).subscribe(
                 val => {
                     console.debug('deleted: ' + val);
                     this.userDeleteSuccessString.current().then(str => this.toast.success(str));
+                    this.usersGrid.reload();
                 },
                 err => {
                     this.userDeleteFailedString.current()
@@ -174,4 +139,4 @@ export class CourseAssociateUsersComponent extends DialogComponent implements On
         });
     }
 
-}
\ No newline at end of file
+}
index 1a3d9c6..54d0c7d 100644 (file)
@@ -1,20 +1,20 @@
 <eg-staff-banner bannerText="Course List" i18n-bannerText>
 </eg-staff-banner>
 
-<eg-string #successString i18n-text text="{{table_name}} Update Succeeded"></eg-string>
-<eg-string #createString i18n-text text="{{table_name}} Was Created Successfully"></eg-string>
-<eg-string #deleteFailedString i18n-text text="Deletion of {{table_name}} failed or was not allowed"></eg-string>
-<eg-string #deleteSuccessString i18n-text text="Deletion of {{table_name}} was successful"></eg-string>
-<eg-string #archiveFailedString i18n-text text="Archival of {{table_name}} failed or was not allowed"></eg-string>
-<eg-string #archiveSuccessString i18n-text text="Archival of {{table_name}} succeeded"></eg-string>
+<eg-string #successString i18n-text text="{{tableName}} Update Succeeded"></eg-string>
+<eg-string #createString i18n-text text="{{tableName}} Was Created Successfully"></eg-string>
+<eg-string #deleteFailedString i18n-text text="Deletion of {{tableName}} failed or was not allowed"></eg-string>
+<eg-string #deleteSuccessString i18n-text text="Deletion of {{tableName}} was successful"></eg-string>
+<eg-string #archiveFailedString i18n-text text="Archival of {{tableName}} failed or was not allowed"></eg-string>
+<eg-string #archiveSuccessString i18n-text text="Archival of {{tableName}} succeeded"></eg-string>
 <eg-string #flairTooltip i18n-text text="Limited Editing"></eg-string>
 
 <div class="w-100 mt-2 mb-2">
-  <eg-grid #grid idlClass={{idl_class}}
+  <eg-grid #grid idlClass={{idlClass}}
     [dataSource]="grid_source"
     [sortable]="true">
     <eg-grid-toolbar-button
-      label="Create {{table_name}}" (onClick)="createNew()" i18n-label>
+      label="Create {{tableName}}" (onClick)="createNew()" i18n-label>
     </eg-grid-toolbar-button>
     <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
     </eg-grid-toolbar-action>
@@ -32,6 +32,7 @@
 
 <eg-fm-record-editor #editDialog
   idlClass="acmc"
+  fieldOrder="course_number,name,owning_lib,section_number"
   [preloadLinkedValues]="true"
   hiddenFields="id,is_archived">
-</eg-fm-record-editor>
\ No newline at end of file
+</eg-fm-record-editor>
index 631c19f..bc439a4 100644 (file)
@@ -1,11 +1,8 @@
 import {Component, Input, ViewChild, OnInit} from '@angular/core';
-import { Router, ActivatedRoute }    from '@angular/router';
+import {Router} from '@angular/router';
 import {IdlObject} from '@eg/core/idl.service';
 import {PcrudService} from '@eg/core/pcrud.service';
-import {AuthService} from '@eg/core/auth.service';
 import {CourseService} from '@eg/staff/share/course.service';
-import {NetService} from '@eg/core/net.service';
-import {OrgService} from '@eg/core/org.service';
 import {GridComponent} from '@eg/share/grid/grid.component';
 import {Pager} from '@eg/share/util/pager';
 import {GridDataSource, GridColumn} from '@eg/share/grid/grid';
@@ -24,8 +21,8 @@ import {CourseAssociateUsersComponent
     templateUrl: './course-list.component.html'
 })
 
-export class CourseListComponent implements OnInit { 
+export class CourseListComponent implements OnInit {
+
     @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
     @ViewChild('grid', { static: true }) grid: GridComponent;
     @ViewChild('successString', { static: true }) successString: StringComponent;
@@ -41,32 +38,28 @@ export class CourseListComponent implements OnInit {
     @ViewChild('courseUserDialog', {static: true})
         private courseUserDialog: CourseAssociateUsersComponent;
 
-    @Input() sort_field: string;
-    @Input() idl_class = "acmc";
+    @Input() sortField: string;
+    @Input() idlClass = 'acmc';
     @Input() dialog_size: 'sm' | 'lg' = 'lg';
-    @Input() table_name = "Course";
+    @Input() tableName = 'Course';
     grid_source: GridDataSource = new GridDataSource();
     currentMaterials: any[] = [];
     search_value = '';
 
     constructor(
-        private auth: AuthService,
         private courseSvc: CourseService,
         private locale: LocaleService,
-        private net: NetService,
-        private org: OrgService,
         private pcrud: PcrudService,
-        private route: ActivatedRoute,
         private router: Router,
         private toast: ToastService
-    ){}
+    ) {}
 
     ngOnInit() {
         this.getSource();
-        this.grid.onRowActivate.subscribe((course:IdlObject) => {
-            let idToEdit = course.id();
+        this.grid.onRowActivate.subscribe((course: IdlObject) => {
+            const idToEdit = course.id();
             this.navigateToCoursePage(idToEdit);
-        })
+        });
     }
 
     /**
@@ -77,31 +70,31 @@ export class CourseListComponent implements OnInit {
             const orderBy: any = {};
             if (sort.length) {
                 // Sort specified from grid
-                orderBy[this.idl_class] = sort[0].name + ' ' + sort[0].dir;
-            } else if (this.sort_field) {
+                orderBy[this.idlClass] = sort[0].name + ' ' + sort[0].dir;
+            } else if (this.sortField) {
                 // Default sort field
-                orderBy[this.idl_class] = this.sort_field;
+                orderBy[this.idlClass] = this.sortField;
             }
             const searchOps = {
                 offset: pager.offset,
                 limit: pager.limit,
                 order_by: orderBy
             };
-            return this.pcrud.retrieveAll(this.idl_class, searchOps, {fleshSelectors: true})
+            return this.pcrud.retrieveAll(this.idlClass, searchOps, {fleshSelectors: true});
         };
     }
 
     navigateToCoursePage(id_arr: IdlObject[]) {
-        if (typeof id_arr == 'number') id_arr = [id_arr];
-        let urls = [];
+        if (typeof id_arr === 'number') { id_arr = [id_arr]; }
+        const urls = [];
         id_arr.forEach(id => {console.log(this.router.url);
             urls.push([this.locale.currentLocaleCode() + this.router.url + '/' +  id]);
         });
-        if (id_arr.length == 1) {
+        if (id_arr.length === 1) {
         this.router.navigate([this.router.url + '/' + id_arr[0]]);
         } else {
             urls.forEach(url => {
-                window.open(url)
+                window.open(url);
             });
         }
     }
@@ -127,9 +120,9 @@ export class CourseListComponent implements OnInit {
 
     editSelected(fields: IdlObject[]) {
         // Edit each IDL thing one at a time
-        let course_ids = [];
+        const course_ids = [];
         fields.forEach(field => {
-            if (typeof field['id'] == 'function') {
+            if (typeof field['id'] === 'function') {
                 course_ids.push(field.id());
             } else {
                 course_ids.push(field['id']);
@@ -140,9 +133,8 @@ export class CourseListComponent implements OnInit {
 
     archiveSelected(course: IdlObject[]) {
         this.courseSvc.disassociateMaterials(course).then(res => {
-            course.forEach(course => {
-                console.log(course);
-                course.is_archived(true);
+            course.forEach(courseToArchive => {
+                courseToArchive.is_archived(true);
             });
             this.pcrud.update(course).subscribe(
                 val => {
@@ -159,12 +151,12 @@ export class CourseListComponent implements OnInit {
         });
     }
 
-    deleteSelected(idl_object: IdlObject[]) {
-        this.courseSvc.disassociateMaterials(idl_object).then(res => {
-            idl_object.forEach(idl_object => {
-                idl_object.isdeleted(true)
+    deleteSelected(idlObject: IdlObject[]) {
+        this.courseSvc.disassociateMaterials(idlObject).then(res => {
+            idlObject.forEach(object => {
+                object.isdeleted(true);
             });
-            this.pcrud.autoApply(idl_object).subscribe(
+            this.pcrud.autoApply(idlObject).subscribe(
                 val => {
                     console.debug('deleted: ' + val);
                     this.deleteSuccessString.current()
@@ -177,6 +169,6 @@ export class CourseListComponent implements OnInit {
                 () => this.grid.reload()
             );
         });
-    };
+    }
 }
 
index 97bff3f..39397d5 100644 (file)
@@ -33,6 +33,7 @@
             mode="update"
             hiddenFieldsList="id,is_archived"
             idlClass="acmc"
+            fieldOrder="course_number,name,owning_lib,section_number"
             [preloadLinkedValues]="true"
             [record]="currentCourse">
           </eg-fm-record-editor>
index 6818a11..df7a41a 100644 (file)
@@ -1,19 +1,8 @@
 import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';
-import {Router, ActivatedRoute} from '@angular/router';
-import {Observable, Observer, of} from 'rxjs';
-import {DialogComponent} from '@eg/share/dialog/dialog.component';
-import {AuthService} from '@eg/core/auth.service';
-import {NetService} from '@eg/core/net.service';
-import {EventService} from '@eg/core/event.service';
-import {OrgService} from '@eg/core/org.service';
+import {ActivatedRoute} from '@angular/router';
 import {PcrudService} from '@eg/core/pcrud.service';
-import {Pager} from '@eg/share/util/pager';
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
-import {GridDataSource} from '@eg/share/grid/grid';
-import {GridComponent} from '@eg/share/grid/grid.component';
 import {IdlObject, IdlService} from '@eg/core/idl.service';
 import {StringComponent} from '@eg/share/string/string.component';
-import {StaffBannerComponent} from '@eg/staff/share/staff-banner.component';
 import {ToastService} from '@eg/share/toast/toast.service';
 import {CourseService} from '@eg/staff/share/course.service';
 import {CourseAssociateUsersComponent} from './course-associate-users.component';
@@ -33,7 +22,7 @@ export class CoursePageComponent implements OnInit {
         private courseMaterialDialog: CourseAssociateMaterialComponent;
     @ViewChild('courseUserDialog', {static: true})
         private courseUserDialog: CourseAssociateUsersComponent;
-    
+
     // Edit Tab
     @ViewChild('archiveFailedString', { static: true })
         archiveFailedString: StringComponent;
@@ -43,12 +32,7 @@ export class CoursePageComponent implements OnInit {
     // Materials Tab
 
     constructor(
-        private auth: AuthService,
         private course: CourseService,
-        private event: EventService,
-        private idl: IdlService,
-        private net: NetService,
-        private org: OrgService,
         private pcrud: PcrudService,
         private route: ActivatedRoute,
         private toast: ToastService
@@ -56,7 +40,7 @@ export class CoursePageComponent implements OnInit {
     }
 
     ngOnInit() {
-        this.courseId = parseInt(this.route.snapshot.paramMap.get('id'));
+        this.courseId = +this.route.snapshot.paramMap.get('id');
         this.course.getCourses([this.courseId]).then(course => {
             this.currentCourse = course[0];
         });
@@ -78,5 +62,5 @@ export class CoursePageComponent implements OnInit {
     }
 
     // Materials Tab
-    
-}
\ No newline at end of file
+
+}
index 7c735dd..fcd9cd5 100644 (file)
@@ -16,4 +16,4 @@ const routes: Routes = [{
   exports: [RouterModule]
 })
 
-export class CourseReservesRoutingModule {}
\ No newline at end of file
+export class CourseReservesRoutingModule {}
index 8eea366..fec6a9e 100644 (file)
@@ -47,7 +47,7 @@ export class ResultRecordComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.searchContext = this.staffCat.searchContext;
-        this.loadCourseInformation(this.summary.id)
+        this.loadCourseInformation(this.summary.id);
         this.isRecordSelected = this.basket.hasRecordId(this.summary.id);
 
         // Watch for basket changes caused by other components
index 62c921c..feb159d 100644 (file)
@@ -18,7 +18,7 @@ import {MultiSelectComponent} from '@eg/share/multi-select/multi-select.componen
 import {NotBeforeMomentValidatorDirective} from '@eg/share/validators/not_before_moment_validator.directive';
 import {PatronBarcodeValidatorDirective} from '@eg/share/validators/patron_barcode_validator.directive';
 import {BroadcastService} from '@eg/share/util/broadcast.service';
-import {CourseService} from './share/course.service'
+import {CourseService} from './share/course.service';
 
 /**
  * Imports the EG common modules and adds modules common to all staff UI's.
@@ -72,7 +72,7 @@ export class StaffCommonModule {
             providers: [ // Export staff-wide services
                 AccessKeyService,
                 AudioService,
-                BroadcastService
+                BroadcastService,
                 CourseService
             ]
         };
index 6089bf2..2aa18b7 100644 (file)
@@ -1,3 +1,4 @@
+import { Observable } from 'rxjs';
 import {Injectable} from '@angular/core';
 import {AuthService} from '@eg/core/auth.service';
 import {EventService} from '@eg/core/event.service';
@@ -19,7 +20,7 @@ export class CourseService {
     ) {}
 
     isOptedIn(): Promise<any> {
-        return new Promise((resolve, reject) => {
+        return new Promise((resolve) => {
             this.org.settings('circ.course_materials_opt_in').then(res => {
                 resolve(res['circ.course_materials_opt_in']);
             });
@@ -45,60 +46,22 @@ export class CourseService {
         }
     }
 
-    getUsers(course_ids?: Number[]): Promise<IdlObject[]> {
+    getUsers(course_ids?: Number[]): Observable<IdlObject> {
+        const flesher = {
+            flesh: 1,
+            flesh_fields: {'acmcu': ['usr']}
+        };
         if (!course_ids) {
             return this.pcrud.retrieveAll('acmcu',
-                {}, {atomic: true}).toPromise();
+                flesher);
         } else {
             return this.pcrud.search('acmcu', {course: course_ids},
-                {}, {atomic: true}).toPromise();
+                flesher);
         }
     }
 
-    fleshMaterial(material): Promise<any> {
-        console.log(material)
-        return new Promise((resolve, reject) => {
-            let item = this.idl.create('acp');
-            this.net.request(
-                'open-ils.circ',
-                'open-ils.circ.copy_details.retrieve',
-                this.auth.token(), material.item()
-            ).subscribe(res => {
-                if (res && res.copy) {
-                    item = res.copy;
-                    item.call_number(res.volume);
-                    item._title = res.mvr.title();
-                    item.circ_lib(this.org.get(item.circ_lib()));
-                    item._id = material.id();
-                    if (material.relationship())
-                        item._relationship = material.relationship();
-                }
-            }, err => {
-                reject(err);
-            }, () => resolve(item));
-        });
-    }
-
-    fleshUser(course_user): Promise<any> {
-        return new Promise((resolve, reject) => {
-            let user = this.idl.create('au');
-            this.net.request(
-                'open-ils.actor',
-                'open-ils.actor.user.fleshed.retrieve',
-                this.auth.token(), course_user.usr()
-            ).subscribe(patron => {
-                user = patron;
-                user._id = course_user.id();
-                if (course_user.usr_role()) user._role = course_user.usr_role();
-                if (course_user.is_public()) user._is_public = course_user.is_public();
-            }, err => {
-                reject(err);
-            }, () => resolve(user));
-        });
-    }
-
     getCoursesFromMaterial(copy_id): Promise<any> {
-        let id_list = [];
+        const id_list = [];
         return new Promise((resolve, reject) => {
 
             return this.pcrud.search('acmcm', {item: copy_id})
@@ -114,15 +77,13 @@ export class CourseService {
                     return this.getCourses(id_list).then(courses => {
                         resolve(courses);
                     });
-                    
                 }
             });
         });
     }
 
     fetchCopiesInCourseFromRecord(record_id) {
-        let cp_list = [];
-        let course_list = [];
+        const cp_list = [];
         return new Promise((resolve, reject) => {
             this.net.request(
                 'open-ils.cat',
@@ -143,13 +104,13 @@ export class CourseService {
 
     // Creating a new acmcm Entry
     associateMaterials(item, args) {
-        let material = this.idl.create('acmcm');
+        const material = this.idl.create('acmcm');
         material.item(item.id());
         if (item.call_number() && item.call_number().record()) {
             material.record(item.call_number().record());
         }
         material.course(args.currentCourse.id());
-        if (args.relationship) material.relationship(args.relationship);
+        if (args.relationship) { material.relationship(args.relationship); }
 
         // Apply temporary fields to the item
         if (args.isModifyingStatus && args.tempStatus) {
@@ -163,12 +124,12 @@ export class CourseService {
         if (args.isModifyingCircMod) {
             material.original_circ_modifier(item.circ_modifier());
             item.circ_modifier(args.tempCircMod);
-            if (!args.tempCircMod) item.circ_modifier(null);
+            if (!args.tempCircMod) { item.circ_modifier(null); }
         }
         if (args.isModifyingCallNumber) {
             material.original_callnumber(item.call_number());
         }
-        let response = {
+        const response = {
             item: item,
             material: this.pcrud.create(material).toPromise()
         };
@@ -177,18 +138,18 @@ export class CourseService {
     }
 
     associateUsers(patron_id, args) {
-        let new_user = this.idl.create('acmcu');
-        if (args.is_public) new_user.is_public(args.is_public);
-        if (args.role) new_user.usr_role(args.role);
+        const new_user = this.idl.create('acmcu');
+        if (args.is_public) { new_user.is_public(args.is_public); }
+        if (args.role) { new_user.usr_role(args.role); }
         new_user.course(args.currentCourse.id());
         new_user.usr(patron_id);
-        return this.pcrud.create(new_user).toPromise()
+        return this.pcrud.create(new_user).toPromise();
     }
 
     disassociateMaterials(courses) {
         return new Promise((resolve, reject) => {
-            let course_ids = [];
-            let course_library_hash = {};
+            const course_ids = [];
+            const course_library_hash = {};
             courses.forEach(course => {
                 course_ids.push(course.id());
                 course_library_hash[course.id()] = course.owning_lib();
@@ -196,14 +157,14 @@ export class CourseService {
             this.pcrud.search('acmcm', {course: course_ids}).subscribe(material => {
                 material.isdeleted(true);
                 this.resetItemFields(material, course_library_hash[material.course()]);
-                this.pcrud.autoApply(material).subscribe(res => {
+                this.pcrud.autoApply(material).subscribe(() => {
                 }, err => {
                     reject(err);
                 }, () => {
                     resolve(material);
                 });
             }, err => {
-                reject(err)
+                reject(err);
             }, () => {
                 resolve(courses);
             });
@@ -212,14 +173,14 @@ export class CourseService {
 
     disassociateUsers(user) {
         return new Promise((resolve, reject) => {
-            let user_ids = [];
-            let course_library_hash = {};
+            const user_ids = [];
+            const course_library_hash = {};
             user.forEach(course => {
                 user_ids.push(course.id());
                 course_library_hash[course.id()] = course.owning_lib();
             });
-            this.pcrud.search('acmcu', {user: user_ids}).subscribe(user => {
-                user.course(user_ids);
+            this.pcrud.search('acmcu', {user: user_ids}).subscribe(u => {
+                u.course(user_ids);
                 this.pcrud.autoApply(user).subscribe(res => {
                     console.debug(res);
                 }, err => {
@@ -228,7 +189,7 @@ export class CourseService {
                     resolve(user);
                 });
             }, err => {
-                reject(err)
+                reject(err);
             }, () => {
                 resolve(user_ids);
             });
@@ -241,7 +202,7 @@ export class CourseService {
             if (material.original_status()) {
                 copy.status(material.original_status());
             }
-            if (copy.circ_modifier() != material.original_circ_modifier()) {
+            if (copy.circ_modifier() !== material.original_circ_modifier()) {
                 copy.circ_modifier(material.original_circ_modifier());
             }
             if (material.original_location()) {
@@ -259,22 +220,22 @@ export class CourseService {
 
     updateItem(item: IdlObject, course_lib, call_number, updatingVolume) {
         return new Promise((resolve, reject) => {
-            this.pcrud.update(item).subscribe(item_id => {
+            this.pcrud.update(item).subscribe(() => {
                 if (updatingVolume) {
-                    let cn = item.call_number();
+                    const cn = item.call_number();
                     return this.net.request(
                         'open-ils.cat', 'open-ils.cat.call_number.find_or_create',
                         this.auth.token(), call_number, cn.record(),
                         course_lib, cn.prefix(), cn.suffix(),
                         cn.label_class()
                     ).subscribe(res => {
-                        let event = this.evt.parse(res);
-                        if (event) return;
+                        const event = this.evt.parse(res);
+                        if (event) { return; }
                         return this.net.request(
                             'open-ils.cat', 'open-ils.cat.transfer_copies_to_volume',
                             this.auth.token(), res.acn_id, [item.id()]
                         ).subscribe(transfered_res => {
-                            console.debug("Copy transferred to volume with code " + transfered_res);
+                            console.debug('Copy transferred to volume with code ' + transfered_res);
                         }, err => {
                             reject(err);
                         }, () => {
@@ -286,9 +247,9 @@ export class CourseService {
                         resolve(item);
                     });
                 } else {
-                    this.pcrud.update(item).subscribe(rse => {
+                    this.pcrud.update(item).subscribe(() => {
                         resolve(item);
-                    }, err => {
+                    }, () => {
                         reject(item);
                     });
                 }
@@ -296,4 +257,4 @@ export class CourseService {
         });
     }
 
-}
\ No newline at end of file
+}
index 6dcaf24..326f80a 100644 (file)
@@ -26,10 +26,10 @@ export interface MarcField {
     // Fields are immutable when it comes to controlfield vs.
     // data field.  Stamp the value when stamping field IDs.
     isCtrlField: boolean;
+    indicator?: (ind: number) => any;
 
     // Pass-through to marcrecord.js
     isControlfield(): boolean;
-    indicator?: (ind: number) => any;
 
     deleteExactSubfields(...subfield: MarcSubfield[]): number;
 }
index a12831c..836a934 100644 (file)
@@ -129,7 +129,8 @@ sub fetch_course_materials {
     if ($self->api_name =~ /\.fleshed/) {
         my $fleshing = {
             'flesh' => 2, 'flesh_fields' => {
-                'acmcm' => ['item', 'record'],
+                'acmcm' => ['item', 'record', 'original_circ_modifier',
+                    'original_location', 'original_status'],
                 'acp' => ['call_number', 'circ_lib', 'location', 'status'],
                 'bre' => ['wide_display_entry'],
             }