<field reporter:label="Course Members" name="members" oils_persist:virtual="true" reporter:datatype="link" />
<field reporter:label="Course Materials" name="materials" oils_persist:virtual="true" reporter:datatype="link" />
<field reporter:label="Non-Cataloged Course Materials" name="non_cat_materials" oils_persist:virtual="true" reporter:datatype="link" />
- <field reporter:label="Is Archived?" name="is_archived" reporter:datatype="boolean" />
+ <field reporter:label="Is Archived?" name="is_archived" reporter:datatype="bool" />
</fields>
<links>
<link field="owning_lib" reltype="has_a" key="id" map="" class="aou" />
<field reporter:label="Course" name="course" reporter:datatype="link" />
<field reporter:label="User" name="usr" reporter:datatype="link" />
<field reporter:label="User Role" name="usr_role" reporter:datatype="text" />
- <field reporter:label="Is Public Viewable?" name="is_public" reporter:datatype="boolean" />
+ <field reporter:label="OPAC Viewable?" name="is_public" reporter:datatype="bool" />
</fields>
<links>
<link field="course" reltype="has_a" key="id" map="" class="acmc" />
-<eg-string #deleteFailedString i18n-text text="Disassociation of Course Material failed or was not allowed"></eg-string>
-<eg-string #deleteSuccessString i18n-text text="Disassociation of Course Material succeeded"></eg-string>
-<eg-string #successString i18n-text text="Association of Course Material succeeded"></eg-string>
-<eg-string #failedString i18n-text text="Association of Course Material failed or was not allowed"></eg-string>
-<eg-string #differentLibraryString i18n-text text="Material exists at a different library"></eg-string>
+
+<eg-string #materialDeleteFailedString i18n-text text="Disassociation of Course Material failed or was not allowed"></eg-string>
+<eg-string #materialDeleteSuccessString i18n-text text="Disassociation of Course Material succeeded"></eg-string>
+<eg-string #materialAddSuccessString i18n-text text="Association of Course Material succeeded"></eg-string>
+<eg-string #materialAddFailedString i18n-text text="Association of Course Material failed or was not allowed"></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>
<ng-template #dialogContent>
- <div class="modal-header bg-info">
- <h4 class="modal-title" i18n>Course Materials</h4>
- <button type="button" class="close"
- i18n-aria-label aria-label="Close" (click)="close()">
- <span aria-hidden="true">×</span>
- </button>
- </div>
- <div class="modal-body">
- <div class="row mt-3">
- <div class="col-md-4">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Barcode</span>
+<div class="modal-header bg-info"
+ [ngClass]="isDialog() ? 'modal-header' : 'alert mt-3'">
+ <h4 class="modal-title" i18n>Course Materials</h4>
+ <ng-container *ngIf="isDialog()">
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </ng-container>
+</div>
+<div [ngClass]="isDialog() ? 'modal-body' : ''">
+ <div class="row">
+ <div [ngClass]="isDialog() ? 'col-md-12' : 'col-md-4'">
+ <div class="row" [ngClass]="isDialog() ? '' : '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>
+ </div>
+ <input type="text" class="flex-grow-1" [(ngModel)]="barcodeInput"
+ (click)="$event.target.select()"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
</div>
- <input type="text" [(ngModel)]="barcodeInput"
- (click)="$event.target.select()"
- (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
</div>
- </div>
- <div class="col-md-5">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Relationship</span>
+ <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>Relationship</span>
+ </div>
+ <input type="text" [(ngModel)]="relationshipInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ placeholder-i18n placeholder="e.g. Required"
+ class="flex-grow-1" />
</div>
- <input type="text" [(ngModel)]="relationshipInput" placeholder-i18n placeholder="e.g. Required" />
</div>
</div>
- <div class="col-md-3">
- <button class="btn btn-outline-dark" (click)="associateItem(barcodeInput, relationshipInput)" i18n [disabled]="!barcodeInput">Add Material</button>
+ <div class="row mt-3">
+ <div class="col-lg-12 text-right">
+ <button class="btn btn-primary"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (click)="associateItem(barcodeInput, relationshipInput)"
+ i18n [disabled]="!barcodeInput">
+ Add Material
+ </button>
+ </div>
</div>
- </div>
- <div class="row justify-content-center mt-3">
- <div class="col">
- <h5 i18n>The following fields will be applied to the material added, and reverted once the course is no longer associated with the material.</h5>
+ <div class="row justify-content-center mt-3">
+ <div class="col">
+ <h5 i18n>The following fields will be applied to the material
+ added, and reverted once the course is no longer associated
+ with the material.</h5>
+ </div>
</div>
- </div>
- <div class="row mt-3">
- <div class="col-md-6">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Call Number</span>
+ <div class="row mt-3">
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Call Number</span>
+ </div>
</div>
- </div>
- <input type="text" [(ngModel)]="tempCallNumber"
- (input)="isModifyingCallNumber = true"/>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingCallNumber"
- aria-label="Checkbox for setting a temporary Call Number" />
+ <input type="text" [(ngModel)]="tempCallNumber"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (input)="isModifyingCallNumber = true" class="flex-grow-1" />
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingCallNumber"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Call Number" />
+ </div>
</div>
</div>
</div>
- </div>
- <div class="col-md-6">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Circulation Modifier</span>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Circulation Modifier</span>
+ </div>
</div>
- </div>
- <eg-combobox i18n-placeholder placeholder="Circulation Modifier..."
- idlClass="ccm" idlField="name" [displayTemplate]="idlClassLabel"
- [asyncSupportsEmptyTermClick]="true"
- (onChange)="tempCircMod = $event.id; isModifyingCircMod = true">
- </eg-combobox>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingCircMod"
- aria-label="Checkbox for setting a temporary Circulation Modifier" />
+ <eg-combobox i18n-placeholder placeholder="Circulation Modifier..."
+ idlClass="ccm" idlField="name" [displayTemplate]="idlClassLabel"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
+ (onChange)="tempCircMod = $event.id; isModifyingCircMod = true">
+ </eg-combobox>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingCircMod"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Circulation Modifier" />
+ </div>
</div>
</div>
</div>
</div>
- </div>
- <div class="row mt-3">
- <div class="col-md-6">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Item Status</span>
+ <div class="row mt-3">
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Item Status</span>
+ </div>
</div>
- </div>
- <eg-combobox i18n-placeholder placeholder="Item Status..."
- idlClass="ccs" idlField="name" [displayTemplate]="idlClassLabel"
- [asyncSupportsEmptyTermClick]="true"
- (onChange)="tempStatus = $event.id; isModifyingStatus = true">
- </eg-combobox>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingStatus"
- aria-label="Checkbox for setting a temporary Item Status" />
+ <eg-combobox i18n-placeholder placeholder="Item Status..."
+ idlClass="ccs" idlField="name" [displayTemplate]="idlClassLabel"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
+ (onChange)="tempStatus = $event.id; isModifyingStatus = true">
+ </eg-combobox>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingStatus"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Item Status" />
+ </div>
</div>
</div>
</div>
- </div>
- <div class="col-md-6">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Shelving Location</span>
+ <div class="d-flex" [ngClass]="isDialog() ? 'col-md-6' : 'col-md-12 mt-3'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Shelving Location</span>
+ </div>
</div>
- </div>
- <eg-item-location-select permFilter="MANAGE_RESERVES"
- [(ngModel)]="tempLocation" (oninput)="isModifyingLocation = true"
- (valueChange)="isModifyingLocation = true">
- </eg-item-location-select>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingLocation"
- aria-label="Checkbox for setting a temporary Shelving Location" />
+ <eg-item-location-select permFilter="MANAGE_RESERVES" class="flex-grow-1"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ [(ngModel)]="tempLocation" (valueChange)="isModifyingLocation = true">
+ </eg-item-location-select>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isModifyingLocation"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for setting a temporary Shelving Location" />
+ </div>
</div>
</div>
</div>
</div>
</div>
- <div class="mt-3">
- <eg-grid #materialGrid [dataSource]="gridDataSource">
- <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
+ <div class="mt-3" [ngClass]="isDialog() ? 'col-md-12' : 'col-md-8'">
+ <eg-grid #materialsGrid [dataSource]="materialsDataSource">
+ <eg-grid-toolbar-action label="Remove Selected" i18n-label (onClick)="deleteSelectedMaterials($event)">
</eg-grid-toolbar-action>
-
- <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-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelectedMaterials($event)">
+ </eg-grid-toolbar-action>
+ <eg-grid-column path="_id" [index]=true [hidden]="true" label="ID" i18n-label></eg-grid-column>
+ <eg-grid-column label="Barcode" i18n-label name="card" [cellTemplate]="barcodeCellTemplate"></eg-grid-column>
<eg-grid-column label="Title" i18n-label name="title" [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>
</eg-grid>
</div>
</div>
- <ng-template #barcodeCellTemplate let-entry="row">
- <span>
- <a class="pl-1"
- href="/eg/staff/cat/item/{{entry.id()}}">
- {{entry.barcode()}}
- </a>
- </span>
- </ng-template>
- <ng-template #titleCellTemplate let-entry="row">
- <span>
- <a class="pl-1"
- href="/eg/staff/cat/catalog/record/{{entry.call_number().record()}}">
- {{entry._title}}
- </a>
- </span>
- </ng-template>
- <ng-template #idlClassLabel let-r="result" i18n>
- {{r.label}}
- </ng-template>
-</ng-template>
\ No newline at end of file
+</div>
+</ng-template>
+<ng-template #barcodeCellTemplate let-entry="row">
+<span>
+ <a class="pl-1"
+ href="/eg/staff/cat/item/{{entry.id()}}">
+ {{entry.barcode()}}
+ </a>
+</span>
+</ng-template>
+<ng-template #titleCellTemplate let-entry="row">
+<span>
+ <a class="pl-1"
+ href="/eg/staff/cat/catalog/record/{{entry.call_number().record()}}">
+ {{entry._title}}
+ </a>
+</span>
+</ng-template>
+<ng-template #idlClassLabel let-r="result" i18n>
+{{r.label}}
+</ng-template>
+
+<ng-container *ngIf="!isDialog()">
+ <!-- in "inline" mode, render the grid pane right here -->
+ <ng-container *ngTemplateOutlet="dialogContent">
+ </ng-container>
+</ng-container>
+
+<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">
+</eg-fm-record-editor>
\ No newline at end of file
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 {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';
})
export class CourseAssociateMaterialComponent extends DialogComponent {
-
+ @Input() currentCourse: IdlObject;
+ @Input() courseId: any;
+ @Input() displayMode: String;
+ materials: any[] = [];
+ @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
@ViewChild('materialsGrid', {static: true}) materialsGrid: GridComponent;
- @ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent;
- @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent;
- @ViewChild('successString', { static: true }) successString: StringComponent;
- @ViewChild('failedString', { static: true }) failedString: StringComponent;
- @ViewChild('differentLibraryString', { static: true }) differentLibraryString: StringComponent;
- @Input() table_name = "Course Materials";
+ @ViewChild('materialDeleteFailedString', { static: true })
+ materialDeleteFailedString: StringComponent;
+ @ViewChild('materialDeleteSuccessString', { static: true })
+ materialDeleteSuccessString: StringComponent;
+ @ViewChild('materialAddSuccessString', { static: true })
+ materialAddSuccessString: StringComponent;
+ @ViewChild('materialAddFailedString', { static: true })
+ materialAddFailedString: StringComponent;
+ @ViewChild('materialEditSuccessString', { static: true })
+ materialEditSuccessString: StringComponent;
+ @ViewChild('materialEditFailedString', { static: true })
+ materialEditFailedString: StringComponent;
+ @ViewChild('materialAddDifferentLibraryString', { static: true })
+ materialAddDifferentLibraryString: StringComponent;
+ materialsDataSource: GridDataSource;
@Input() barcodeInput: String;
@Input() relationshipInput: String;
@Input() tempCallNumber: String;
@Input() isModifyingCircMod: Boolean;
@Input() isModifyingCallNumber: Boolean;
@Input() isModifyingLocation: Boolean;
- currentCourse: IdlObject;
- materials: any[];
- gridDataSource: GridDataSource;
constructor(
private auth: AuthService,
+ private course: CourseService,
+ private event: EventService,
private idl: IdlService,
private net: NetService,
- private pcrud: PcrudService,
private org: OrgService,
- private evt: EventService,
- private modal: NgbModal,
+ private pcrud: PcrudService,
+ private route: ActivatedRoute,
private toast: ToastService,
- private courseSvc: CourseService
+ private modal: NgbModal
) {
super(modal);
- this.gridDataSource = new GridDataSource();
+ this.materialsDataSource = new GridDataSource();
}
ngOnInit() {
- this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
- return this.fetchMaterials(pager);
+ this.materialsDataSource.getRows = (pager: Pager, sort: any[]) => {
+ return this.loadMaterialsGrid(pager);
}
}
- deleteSelected(items) {
- let item_ids = [];
- items.forEach(item => {
- this.gridDataSource.data.splice(this.gridDataSource.data.indexOf(item, 0), 1);
- item_ids.push(item.id())
+ isDialog(): boolean {
+ return this.displayMode === 'dialog';
+ }
+
+ loadMaterialsGrid(pager: Pager): Observable<any> {
+ return new Observable<any>(observer => {
+ this.course.getMaterials(this.courseId).then(materials => {
+ materials.forEach(material => {
+ this.course.fleshMaterial(material).then(fleshed_material => {
+ this.materialsDataSource.data.push(fleshed_material);
+ });
+ });
+ });
+ observer.complete();
});
- this.pcrud.search('acmcm', {course: this.currentCourse.id(), item: item_ids}).subscribe(material => {
- material.isdeleted(true);
- this.pcrud.autoApply(material).subscribe(
- val => {
- this.courseSvc.resetItemFields(material, this.currentCourse.owning_lib());
- console.debug('deleted: ' + val);
- this.deleteSuccessString.current().then(str => this.toast.success(str));
+ }
+
+ editSelectedMaterials(itemFields: IdlObject[]) {
+ // Edit each IDL thing one at a time
+ const editOneThing = (item: IdlObject) => {
+ if (!item) { return; }
+
+ this.showEditDialog(item).then(
+ () => editOneThing(itemFields.shift()));
+ };
+
+ editOneThing(itemFields.shift());
+ }
+
+ showEditDialog(course_material: IdlObject): Promise<any> {
+ this.editDialog.mode = 'update';
+ this.editDialog.recordId = course_material._id;
+ return new Promise((resolve, reject) => {
+ this.editDialog.open({size: 'lg'}).subscribe(
+ result => {
+ this.materialEditSuccessString.current()
+ .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
+ );
+ } else {
+ course_material._relationship = material.relationship();
+ }
+ });
+ resolve(result);
},
- err => {
- this.deleteFailedString.current()
+ error => {
+ this.materialEditFailedString.current()
.then(str => this.toast.danger(str));
+ reject(error);
}
);
});
}
-
+
associateItem(barcode, relationship) {
if (barcode) {
let args = {
currentCourse: this.currentCourse
}
this.barcodeInput = null;
-
+
this.pcrud.search('acp', {barcode: args.barcode}, {
flesh: 3, flesh_fields: {acp: ['call_number']}
}).subscribe(item => {
- let associatedMaterial = this.courseSvc.associateMaterials(item, args);
+ let associatedMaterial = this.course.associateMaterials(item, args);
associatedMaterial.material.then(res => {
item = associatedMaterial.item;
let new_cn = item.call_number().label();
if (this.tempCallNumber) new_cn = this.tempCallNumber;
- this.courseSvc.updateItem(item,
- this.currentCourse.owning_lib(),
- new_cn, args.isModifyingCallNumber).then(resp => {
- this.fetchItem(item.id(), args.relationship);
+ this.course.updateItem(item, this.currentCourse.owning_lib(),
+ new_cn, args.isModifyingCallNumber
+ ).then(resp => {
+ this.course.fleshMaterial(res).then(fleshed_material => {
+ this.materialsDataSource.data.push(fleshed_material);
+ });
if (item.circ_lib() != this.currentCourse.owning_lib()) {
- this.differentLibraryString.current().then(str => this.toast.warning(str));
+ this.materialAddDifferentLibraryString.current()
+ .then(str => this.toast.warning(str));
} else {
- this.successString.current().then(str => this.toast.success(str));
+ this.materialAddSuccessString.current()
+ .then(str => this.toast.success(str));
}
});
}, err => {
- this.failedString.current().then(str => this.toast.danger(str));
+ this.materialAddFailedString.current()
+ .then(str => this.toast.danger(str));
});
});
}
}
- fetchMaterials(pager: Pager): Observable<any> {
- return new Observable<any>(observer => {
- this.materials.forEach(material => {
- this.fetchItem(material.item, material.relationship);
- });
- observer.complete();
+ deleteSelectedMaterials(items) {
+ let item_ids = [];
+ items.forEach(item => {
+ this.materialsDataSource.data.splice(this.materialsDataSource.data.indexOf(item, 0), 1);
+ item_ids.push(item.id())
});
- }
-
- fetchItem(itemId, relationship): Promise<any> {
- return new Promise((resolve, reject) => {
- this.net.request(
- 'open-ils.circ',
- 'open-ils.circ.copy_details.retrieve',
- this.auth.token(), itemId
- ).subscribe(res => {
- if (res) {
- let item = res.copy;
- item.call_number(res.volume);
- item.circ_lib(this.org.get(item.circ_lib()));
- item._title = res.mvr.title();
- item._relationship = relationship;
- this.gridDataSource.data.push(item);
+ this.pcrud.search('acmcm', {course: this.courseId, item: item_ids}).subscribe(material => {
+ material.isdeleted(true);
+ this.pcrud.autoApply(material).subscribe(
+ val => {
+ this.course.resetItemFields(material, this.currentCourse.owning_lib());
+ console.debug('deleted: ' + val);
+ this.materialDeleteSuccessString.current().then(str => this.toast.success(str));
+ },
+ err => {
+ this.materialDeleteFailedString.current()
+ .then(str => this.toast.danger(str));
}
- }, err => {
- reject(err);
- }, () => resolve(this.gridDataSource.data));
+ );
});
}
}
\ No newline at end of file
-<eg-string #deleteFailedString i18n-text text="Disassociation of Course Material failed or was not allowed"></eg-string>
-<eg-string #deleteSuccessString i18n-text text="Disassociation of Course Material succeeded"></eg-string>
-<eg-string #successString i18n-text text="Association of Course Material succeeded"></eg-string>
-<eg-string #failedString i18n-text text="Association of Course Material failed or was not allowed"></eg-string>
-<eg-string #differentLibraryString i18n-text text="Material exists at a different library"></eg-string>
+<eg-string #userDeleteFailedString i18n-text text="Removal of User failed or was not allowed"></eg-string>
+<eg-string #userDeleteSuccessString i18n-text text="Removal of User succeeded"></eg-string>
+<eg-string #userAddSuccessString i18n-text text="Addition of User succeeded"></eg-string>
+<eg-string #userAddFailedString i18n-text text="Addition of User failed or was not allowed"></eg-string>
+<eg-string #userEditSuccessString i18n-text text="Update of User succeeded"></eg-string>
+<eg-string #userEditFailedString i18n-text text="Update of User failed or was not allowed"></eg-string>
<ng-template #dialogContent>
- <div class="modal-header bg-info">
- <h4 class="modal-title" i18n>Course Users</h4>
- <button type="button" class="close"
- i18n-aria-label aria-label="Close" (click)="close()">
- <span aria-hidden="true">×</span>
- </button>
- </div>
- <div class="modal-body">
- <div class="row mt-4">
- <div class="col-md-5">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>User Role</span>
+<div class="modal-header bg-info"
+ [ngClass]="isDialog() ? 'modal-header' : 'alert mt-3'">
+ <h4 class="modal-title" i18n>Course Users</h4>
+ <ng-container *ngIf="isDialog()">
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close" (click)="close()">
+ <span aria-hidden="true">×</span>
+ </button>
+ </ng-container>
+</div>
+<div [ngClass]="isDialog() ? 'modal-body' : ''">
+ <div class="row">
+ <div [ngClass]="isDialog() ? 'col-md-12' : 'col-md-4'">
+ <div class="row" [ngClass]="isDialog() ? '' : '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>Patron Barcode</span>
+ </div>
+ <input type="text" class="flex-grow-1" [(ngModel)]="userBarcode"
+ (click)="$event.target.select()"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ (keyup.enter)="associateUser(userBarcode)" />
</div>
- <input type="text" [(ngModel)]="userRoleInput" />
</div>
- </div>
- <div class="row justify-content-center mt-3">
- </div>
- <div class="col-md-6">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>User</span>
+ <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>
</div>
+ <input type="text" [(ngModel)]="userRoleInput"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ placeholder-i18n placeholder="e.g. Student, TA, Instructor..."
+ class="flex-grow-1" />
</div>
- <input type="text" [(ngModel)]="new_usr" />
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div [ngClass]="isDialog() ? 'offset-md-6 col-md-4' : 'col-md-6'">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <div class="input-group-text">
+ <span i18n>Is Public Role?</span>
+ </div>
+ </div>
+ <div class="input-group-append">
+ <div class="input-group-text">
+ <input type="checkbox" [(ngModel)]="isPublicRole"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ aria-label="Checkbox for allowing user to display on the OPAC Course Page" />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="text-right" [ngClass]="isDialog() ? 'col-md-2' : 'col-md-6'">
+ <button class="btn btn-primary"
+ [disabled]="currentCourse && currentCourse.is_archived() == 't'"
+ i18n [disabled]="!userBarcode" (click)="associateUser(userBarcode)">
+ Add User
+ </button>
</div>
</div>
</div>
- <div class="col-md-0">
- <button class="btn btn-outline-dark" (click)="associateUsers(userRoleInput)" i18n [disabled]="!new_usr || !userRoleInput">Add User</button>
- </div>
- <div class="mt-3">
- <eg-grid #usersGrid [dataSource]="gridDataSource">
- <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
+ <div class="mt-3" [ngClass]="isDialog() ? 'col-md-12' : 'col-md-8'">
+ <eg-grid #usersGrid [dataSource]="usersDataSource">
+ <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" [index]=true [hidden]="true" i18n-label></eg-grid-column>
- <eg-grid-column label="User Role" name="_usr_role" i18n-label></eg-grid-column>
+ <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="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>
</div>
- </div>
\ No newline at end of file
+ </div>
+</div>
+</ng-template>
+
+<ng-container *ngIf="!isDialog()">
+ <!-- in "inline" mode, render the grid pane right here -->
+ <ng-container *ngTemplateOutlet="dialogContent">
+ </ng-container>
+</ng-container>
+
+<eg-fm-record-editor #editDialog
+ idlClass='acmcu'
+ [fieldOptions]="{course: {linkedSearchField: 'course_number'}}"
+ [preloadLinkedValues]="true"
+ hiddenFields="id,usr">
+</eg-fm-record-editor>
\ No newline at end of file
-import {Component, Input, ViewChild, OnInit, TemplateRef} from '@angular/core';\r
-import {Observable, Observer, of} from 'rxjs';\r
-import {DialogComponent} from '@eg/share/dialog/dialog.component';\r
-import {AuthService} from '@eg/core/auth.service';\r
-import {NetService} from '@eg/core/net.service';\r
-import {EventService} from '@eg/core/event.service';\r
-import {OrgService} from '@eg/core/org.service';\r
-import {PcrudService} from '@eg/core/pcrud.service';\r
-import {Pager} from '@eg/share/util/pager';\r
-import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';\r
-import {GridDataSource} from '@eg/share/grid/grid';\r
-import {GridComponent} from '@eg/share/grid/grid.component';\r
-import {IdlObject, IdlService} from '@eg/core/idl.service';\r
-import {StringComponent} from '@eg/share/string/string.component';\r
-import {ToastService} from '@eg/share/toast/toast.service';\r
-import {CourseService} from '@eg/staff/share/course.service';\r
-\r
-@Component({\r
- selector: 'eg-course-associate-users-dialog',\r
- templateUrl: './course-associate-users.component.html'\r
-})\r
-\r
-export class CourseAssociateUsersComponent extends DialogComponent {\r
-\r
- @ViewChild('usersGrid', {static: true}) usersGrid: GridComponent;\r
- @ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent;\r
- @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent;\r
- @ViewChild('successString', { static: true }) successString: StringComponent;\r
- @ViewChild('failedString', { static: true }) failedString: StringComponent;\r
- @ViewChild('differentLibraryString', { static: true }) differentLibraryString: StringComponent;\r
- @Input() table_name = "Course Users";\r
- @Input() userRoleInput: String;\r
- \r
- idl_class = "acmcu";\r
- new_usr:any;\r
- currentCourse: IdlObject;\r
- users: any[];\r
- gridDataSource: GridDataSource;\r
-\r
- constructor(\r
- private auth: AuthService,\r
- private idl: IdlService,\r
- private net: NetService,\r
- private pcrud: PcrudService,\r
- private org: OrgService,\r
- private evt: EventService,\r
- private modal: NgbModal,\r
- private toast: ToastService,\r
- private courseSvc: CourseService\r
- ) {\r
- super(modal);\r
- this.gridDataSource = new GridDataSource();\r
- }\r
-\r
- ngOnInit() {\r
- }\r
-\r
- /**\r
- * Takes the user id and creates a course user based around it.\r
- * @param user_input The inputted user Id.\r
- */\r
- associateUsers(user_input) {\r
- if (user_input) {\r
- let user = this.idl.create('acmcu');\r
- user.course(this.currentCourse.id());\r
- user.usr(this.new_usr);\r
- user.usr_role(user_input);\r
- this.pcrud.create(user).subscribe(\r
- val => {\r
- console.debug('created: ' + val);\r
- this.successString.current().then(str => this.toast.success(str));\r
- }, err => {\r
- this.failedString.current().then(str => this.toast.danger(str));\r
- })\r
- }\r
- }\r
-\r
- /**\r
- * Delete a user based on the id selected from the grid.\r
- * @param users \r
- */\r
- deleteSelected(users) {\r
- let user_ids = [];\r
- users.forEach(user => {\r
- this.gridDataSource.data.splice(this.gridDataSource.data.indexOf(user, 0), 1);\r
- user_ids.push(user.id())\r
- });\r
- this.pcrud.remove(users).subscribe(user => {\r
- this.pcrud.autoApply(user).subscribe(\r
- val => {\r
- console.debug('deleted: ' + val);\r
- this.deleteSuccessString.current().then(str => this.toast.success(str));\r
- },\r
- err => {\r
- this.deleteFailedString.current()\r
- .then(str => this.toast.danger(str));\r
- }\r
- );\r
- });\r
- }\r
-\r
+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 {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 {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';
+
+@Component({
+ selector: 'eg-course-associate-users-dialog',
+ templateUrl: './course-associate-users.component.html'
+})
+
+export class CourseAssociateUsersComponent extends DialogComponent implements OnInit {
+ @Input() currentCourse: IdlObject;
+ @Input() courseId: any;
+ @Input() displayMode: String;
+ users: any[] = [];
+ @ViewChild('editDialog', { static: true }) editDialog: FmRecordEditorComponent;
+ @ViewChild('usersGrid', {static: true}) usersGrid: GridComponent;
+ @ViewChild('userDeleteFailedString', { static: true })
+ userDeleteFailedString: StringComponent;
+ @ViewChild('userDeleteSuccessString', { static: true })
+ userDeleteSuccessString: StringComponent;
+ @ViewChild('userAddSuccessString', { static: true })
+ userAddSuccessString: StringComponent;
+ @ViewChild('userAddFailedString', { static: true })
+ userAddFailedString: StringComponent;
+ @ViewChild('userEditSuccessString', { static: true })
+ userEditSuccessString: StringComponent;
+ @ViewChild('userEditFailedString', { static: true })
+ userEditFailedString: StringComponent;
+ usersDataSource: GridDataSource;
+ @Input() userBarcode: String;
+ @Input() userRoleInput: String;
+ @Input() 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
+ ) {
+ super(modal);
+ this.usersDataSource = new GridDataSource();
+ }
+
+ ngOnInit() {
+ this.usersDataSource.getRows = (pager: Pager, sort: any[]) => {
+ return this.loadUsersGrid(pager);
+ }
+ }
+
+ 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 = {
+ currentCourse: this.currentCourse,
+ barcode: barcode,
+ 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
+ ).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));
+ });
+ }, err => {
+ this.userAddFailedString.current().then(str => this.toast.danger(str));
+ });
+ });
+ }
+ }
+
+ editSelectedUsers(userFields: IdlObject[]) {
+ // Edit each IDL thing one at a time
+ const editOneThing = (user: IdlObject) => {
+ if (!user) { return; }
+
+ this.showEditDialog(user).then(
+ () => editOneThing(userFields.shift()));
+ };
+
+ editOneThing(userFields.shift());
+ }
+
+ showEditDialog(user: IdlObject): Promise<any> {
+ this.editDialog.mode = 'update';
+ this.editDialog.recordId = user._id;
+ return new Promise((resolve, reject) => {
+ this.editDialog.open({size: 'lg'}).subscribe(
+ 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();
+ }
+ });
+ resolve(result);
+ },
+ error => {
+ this.userEditFailedString.current()
+ .then(str => this.toast.danger(str));
+ reject(error);
+ }
+ );
+ });
+ }
+
+ deleteSelectedUsers(users) {
+ let user_ids = [];
+ users.forEach(user => {
+ this.usersDataSource.data.splice(this.usersDataSource.data.indexOf(user, 0), 1);
+ user_ids.push(user.id())
+ });
+ 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));
+ },
+ err => {
+ this.userDeleteFailedString.current()
+ .then(str => this.toast.danger(str));
+ }
+ );
+ });
+ }
+
}
\ No newline at end of file
<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 #deleteSucailedString i18n-text text="Deletion of {{table_name}} was successful"></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 #flairTooltip i18n-text text="Limited Editing"></eg-string>
-<eg-course-associate-material-dialog #courseMaterialDialog>
-</eg-course-associate-material-dialog>
-
-<eg-course-associate-users-dialog #courseUserDialog>
-</eg-course-associate-users-dialog>
-
<div class="w-100 mt-2 mb-2">
<eg-grid #grid idlClass={{idl_class}}
[dataSource]="grid_source"
<eg-grid-toolbar-button
label="Create {{table_name}}" (onClick)="createNew()" i18n-label>
</eg-grid-toolbar-button>
- <eg-grid-toolbar-action label="View Materials" i18n-label (onClick)="openMaterialsDialog($event)">
- </eg-grid-toolbar-action>
<eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
</eg-grid-toolbar-action>
<eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
</eg-grid-toolbar-action>
<eg-grid-toolbar-action label="Archive Selected" i18n-label (onClick)="archiveSelected($event)">
</eg-grid-toolbar-action>
- <eg-grid-toolbar-action label="View Users" i18n-label (onClick)="openUsersDialog($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="Title" name="name" i18n-label></eg-grid-column>
+ <eg-grid-column label="CourseNumber" name="course_number" i18n-label></eg-grid-column>
+ <eg-grid-column label="Section Number" name="section_number" i18n-label></eg-grid-column>
+ <eg-grid-column label="Is Archived?" name="is_archived" i18n-label datatype="bool"></eg-grid-column>
</eg-grid>
</div>
<eg-fm-record-editor #editDialog
- idlClass={{idl_class}}
+ idlClass="acmc"
[preloadLinkedValues]="true"
- hiddenFields="is_archived,id">
-</eg-fm-record-editor>
+ hiddenFields="id,is_archived">
+</eg-fm-record-editor>
\ No newline at end of file
import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
import {StringComponent} from '@eg/share/string/string.component';
import {ToastService} from '@eg/share/toast/toast.service';
+import {LocaleService} from '@eg/core/locale.service';
import {CourseAssociateMaterialComponent
} from './course-associate-material.component';
constructor(
private auth: AuthService,
private courseSvc: CourseService,
+ private locale: LocaleService,
private net: NetService,
private org: OrgService,
private pcrud: PcrudService,
};
}
- navigateToCoursePage(id: any) {
- this.router.navigate(["/staff/admin/local/asset/course_list/" + id]);
- }
-
- showEditDialog(standingPenalty: IdlObject): Promise<any> {
- this.editDialog.mode = 'update';
- this.editDialog.recordId = standingPenalty['id']();
- return new Promise((resolve, reject) => {
- this.editDialog.open({size: this.dialog_size}).subscribe(
- result => {
- this.successString.current()
- .then(str => this.toast.success(str));
- this.grid.reload();
- resolve(result);
- },
- error => {
- this.updateFailedString.current()
- .then(str => this.toast.danger(str));
- reject(error);
- }
- );
+ navigateToCoursePage(id_arr: IdlObject[]) {
+ if (typeof id_arr == 'number') id_arr = [id_arr];
+ let urls = [];
+ id_arr.forEach(id => {console.log(this.router.url);
+ urls.push([this.locale.currentLocaleCode() + this.router.url + '/' + id]);
});
+ if (id_arr.length == 1) {
+ this.router.navigate([this.router.url + '/' + id_arr[0]]);
+ } else {
+ urls.forEach(url => {
+ window.open(url)
+ });
+ }
}
createNew() {
editSelected(fields: IdlObject[]) {
// Edit each IDL thing one at a time
- const editOneThing = (field_object: IdlObject) => {
- if (!field_object) { return; }
- this.showEditDialog(field_object).then(
- () => editOneThing(fields.shift()));
- };
- editOneThing(fields.shift());
+ let course_ids = [];
+ fields.forEach(field => {
+ if (typeof field['id'] == 'function') {
+ course_ids.push(field.id());
+ } else {
+ course_ids.push(field['id']);
+ }
+ });
+ this.navigateToCoursePage(course_ids);
}
archiveSelected(course: IdlObject[]) {
);
});
};
-
- fetchCourseMaterials(course, currentMaterials): Promise<any> {
- return new Promise((resolve, reject) => {
- this.pcrud.search('acmcm', {course: course}).subscribe(res => {
- if (res) this.fleshItemDetails(res.item(), res.relationship());
- }, err => {
- reject(err);
- }, () => resolve(this.courseMaterialDialog.gridDataSource.data));
- });
- }
-
- /**
- * Uses the course id to fetch the different users associated with that course.
- * @param course The course id
- * @param currentMaterials
- */
- fetchCourseUsers(course, currentMaterials): Promise<any> {
- return new Promise((resolve, reject) => {
- this.pcrud.search('acmcu', {course: course}).subscribe(res => {
- if(res) this.fleshUserDetails(res.usr(), res.usr_role());
- }, err => {
- reject(err);
- }, () => resolve(this.courseUserDialog.gridDataSource.data));
- });
- }
-
- /**
- * Takes the user id from the course table, and cross references that with the user table,
- * to find the right data.
- * @param userId The user id that is to be cross referenced.
- * @param usr_role The user role that is to be added to the grid.
- */
- fleshUserDetails(userId, usr_role) {
- return new Promise((resolve, reject) => {
- this.pcrud.search("au", {id:userId}).subscribe(res => {
- if (res) {
- let user = res;
- user._usr_role = usr_role;
- this.courseUserDialog.gridDataSource.data.push(user);
- }
- }, err => {
- reject(err);
- }, () => resolve(this.courseMaterialDialog.gridDataSource.data));
- });
- }
-
- fleshItemDetails(itemId, relationship): Promise<any> {
- return new Promise((resolve, reject) => {
- this.net.request(
- 'open-ils.circ',
- 'open-ils.circ.copy_details.retrieve',
- this.auth.token(), itemId
- ).subscribe(res => {
- if (res) {
- let item = res.copy;
- item.call_number(res.volume);
- item._title = res.mvr.title();
- item.circ_lib(this.org.get(item.circ_lib()));
- item._relationship = relationship;
- this.courseMaterialDialog.gridDataSource.data.push(item);
- }
- }, err => {
- reject(err);
- }, () => resolve(this.courseMaterialDialog.gridDataSource.data));
- });
- }
-
- openMaterialsDialog(course) {
- let currentMaterials = []
- this.courseMaterialDialog.gridDataSource.data = [];
- this.fetchCourseMaterials(course[0].id(), currentMaterials).then(res => {
- this.courseMaterialDialog.currentCourse = course[0];
- this.courseMaterialDialog.materials = currentMaterials;
- this.courseMaterialDialog.open({size: 'lg'}).subscribe(res => {
- console.log(res);
- });
- });
- }
-
- /**
- * Opens the user dialog component using the course id
- * @param course
- */
- openUsersDialog(course) {
- let currentUsers = []
- this.courseUserDialog.gridDataSource.data = [];
- this.fetchCourseUsers(course[0].id(), currentUsers).then(res => {
- this.courseUserDialog.currentCourse = course[0];
- this.courseUserDialog.users = currentUsers;
- this.courseUserDialog.open({size: 'lg'}).subscribe(res => {
- console.log(res);
- });
- });
- }
}
[bannerStyle]="currentCourse.is_archived() == 't' ? 'alert-secondary' : null"
[bannerIcon]="currentCourse.is_archived() == 't' ? 'lock' : null">
</eg-staff-banner>
+
+<div class="row">
+ <div class="col text-right">
+ <button class="btn btn-danger"
+ (click)="archiveCourse()" [disabled]="currentCourse && currentCourse.is_archived() == 't'">
+ <i class="material-icons align-middle"
+ *ngIf="currentCourse && currentCourse.is_archived() == 't'">block</i>
+ <span class="align-middle">Archive Course</span>
+ </button>
+ <a class="btn btn-warning ml-3" routerLink="/staff/admin/local/asset/course_list" i18n>
+ <i class="material-icons align-middle">keyboard_return</i>
+ <span class="align-middle">Return to Course List</span>
+ </a>
+ </div>
+</div>
<ngb-tabset class="mb-3">
<!-- Edit Tab -->
<ng-template ngbTabContent>
<div class="row">
<div class="col-lg-3 mt-3">
- <button class="p-2 mb-3 btn btn-danger btn-lg"
- (click)="archiveCourse()" [disabled]="currentCourse && currentCourse.is_archived() == 't'">
- <i class="material-icons align-middle"
- *ngIf="currentCourse && currentCourse.is_archived() == 't'">block</i>
- <span class="align-middle">Archive Course</span>
- </button>
</div>
<div class="col-lg-6 mt-3">
<eg-fm-record-editor displayMode="inline"
<!-- Materials Tab -->
<ngb-tab title="Course Materials" i18n-title id="courseMaterials">
<ng-template ngbTabContent>
- <div class="row mt-3">
- <!-- Input Sidebar -->
- <div class="col-lg-4 mt-3">
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Barcode</span>
- </div>
- <input type="text" class="flex-grow-1" [(ngModel)]="barcodeInput"
- (click)="$event.target.select()"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- (keyup.enter)="associateItem(barcodeInput, relationshipInput)" />
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Relationship</span>
- </div>
- <input type="text" [(ngModel)]="relationshipInput"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- placeholder-i18n placeholder="e.g. Required"
- class="flex-grow-1" />
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 text-right">
- <button class="btn btn-primary"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- (click)="associateItem(barcodeInput, relationshipInput)"
- i18n [disabled]="!barcodeInput">
- Add Material
- </button>
- </div>
- </div>
- <div class="row justify-content-center mt-3">
- <div class="col">
- <h5 i18n>The following fields will be applied to the material
- added, and reverted once the course is no longer associated
- with the material.</h5>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Call Number</span>
- </div>
- </div>
- <input type="text" [(ngModel)]="tempCallNumber"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- (input)="isModifyingCallNumber = true" class="flex-grow-1" />
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingCallNumber"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Call Number" />
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Circulation Modifier</span>
- </div>
- </div>
- <eg-combobox i18n-placeholder placeholder="Circulation Modifier..."
- idlClass="ccm" idlField="name" [displayTemplate]="idlClassLabel"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
- (onChange)="tempCircMod = $event.id; isModifyingCircMod = true">
- </eg-combobox>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingCircMod"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Circulation Modifier" />
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Item Status</span>
- </div>
- </div>
- <eg-combobox i18n-placeholder placeholder="Item Status..."
- idlClass="ccs" idlField="name" [displayTemplate]="idlClassLabel"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- [asyncSupportsEmptyTermClick]="true" class="flex-grow-1"
- (onChange)="tempStatus = $event.id; isModifyingStatus = true">
- </eg-combobox>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingStatus"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Item Status" />
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <div class="input-group-text">
- <span i18n>Shelving Location</span>
- </div>
- </div>
- <eg-item-location-select permFilter="MANAGE_RESERVES" class="flex-grow-1"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- [(ngModel)]="tempLocation" (valueChange)="isModifyingLocation = true">
- </eg-item-location-select>
- <div class="input-group-append">
- <div class="input-group-text">
- <input type="checkbox" [(ngModel)]="isModifyingLocation"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- aria-label="Checkbox for setting a temporary Shelving Location" />
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- End Input Sidebar -->
- <div class="col-lg-8 mt-3">
- <eg-grid #materialsGrid [dataSource]="materialsDataSource">
- <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
- </eg-grid-toolbar-action>
-
- <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" [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>
- <eg-grid-column path="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="_relationship" label="Relationship" i18n-label></eg-grid-column>
- </eg-grid>
- </div>
- </div>
- <ng-template #barcodeCellTemplate let-entry="row">
- <span>
- <a class="pl-1"
- href="/eg/staff/cat/item/{{entry.id()}}">
- {{entry.barcode()}}
- </a>
- </span>
- </ng-template>
- <ng-template #titleCellTemplate let-entry="row">
- <span>
- <a class="pl-1"
- href="/eg/staff/cat/catalog/record/{{entry.call_number().record()}}">
- {{entry._title}}
- </a>
- </span>
- </ng-template>
- <ng-template #idlClassLabel let-r="result" i18n>
- {{r.label}}
- </ng-template>
+ <eg-course-associate-material-dialog [courseId]="courseId"
+ [currentCourse]="currentCourse" displayMode="inline">
+ </eg-course-associate-material-dialog>
</ng-template>
</ngb-tab>
<!-- Users Tab -->
<ngb-tab title="Course Users" i18n-title id="courseUsers">
<ng-template ngbTabContent>
- <div class="row mt-3">
- <div class="col-lg-4 mt-3">
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Patron Barcode</span>
- </div>
- <input type="text" class="flex-grow-1" [(ngModel)]="userBarcode"
- (click)="$event.target.select()"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'" />
- <!--(keyup.enter)="associateUser(userBarcode)"-->
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 d-flex">
- <div class="input-group">
- <div class="input-group-prepend">
- <span class="input-group-text" i18n>Role</span>
- </div>
- <input type="text" [(ngModel)]="userRoleInput"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- placeholder-i18n placeholder="e.g. Student, TA, Instructor..."
- class="flex-grow-1" />
- </div>
- </div>
- </div>
- <div class="row mt-3">
- <div class="col-lg-12 text-right">
- <!--(click)="associateUser(userBarcode)"-->
- <button class="btn btn-primary"
- [disabled]="currentCourse && currentCourse.is_archived() == 't'"
- i18n [disabled]="!userBarcode">
- Add User
- </button>
- </div>
- </div>
- </div>
- <div class="col-lg-8 mt-3">
- <!-- eg-grid -->
- </div>
- </div>
+ <eg-course-associate-users-dialog [courseId]="courseId"
+ [currentCourse]="currentCourse" displayMode="inline">
+ </eg-course-associate-users-dialog>
</ng-template>
</ngb-tab>
</ngb-tabset>
<eg-string #archiveFailedString i18n-text text="Archival of Course failed or was not allowed"></eg-string>
-<eg-string #archiveSuccessString i18n-text text="Archival of Course succeeded"></eg-string>
-<eg-string #materialDeleteFailedString i18n-text text="Disassociation of Course Material failed or was not allowed"></eg-string>
-<eg-string #materialDeleteSuccessString i18n-text text="Disassociation of Course Material succeeded"></eg-string>
-<eg-string #materialAddSuccessString i18n-text text="Association of Course Material succeeded"></eg-string>
-<eg-string #materialAddFailedString i18n-text text="Association of Course Material failed or was not allowed"></eg-string>
-<eg-string #MaterialAddDifferentLibraryString i18n-text text="Material exists at a different library"></eg-string>
\ No newline at end of file
+<eg-string #archiveSuccessString i18n-text text="Archival of Course succeeded"></eg-string>
\ No newline at end of file
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';
+import {CourseAssociateMaterialComponent} from './course-associate-material.component';
@Component({
selector: 'eg-course-page',
currentCourse: IdlObject;
courseId: any;
+
+ @ViewChild('courseMaterialDialog', {static: true})
+ private courseMaterialDialog: CourseAssociateMaterialComponent;
+ @ViewChild('courseUserDialog', {static: true})
+ private courseUserDialog: CourseAssociateUsersComponent;
// Edit Tab
@ViewChild('archiveFailedString', { static: true })
archiveSuccessString: StringComponent;
// Materials Tab
- materials: any[] = [];
- @ViewChild('materialsGrid', {static: true}) materialsGrid: GridComponent;
- @ViewChild('materialDeleteFailedString', { static: true })
- materialDeleteFailedString: StringComponent;
- @ViewChild('materialDeleteSuccessString', { static: true })
- materialDeleteSuccessString: StringComponent;
- @ViewChild('materialAddSuccessString', { static: true })
- materialAddSuccessString: StringComponent;
- @ViewChild('materialAddFailedString', { static: true })
- materialAddFailedString: StringComponent;
- @ViewChild('materialAddDifferentLibraryString', { static: true })
- materialAddDifferentLibraryString: StringComponent;
- materialsDataSource: GridDataSource;
- @Input() barcodeInput: String;
- @Input() relationshipInput: String;
- @Input() tempCallNumber: String;
- @Input() tempStatus: Number;
- @Input() tempLocation: Number;
- @Input() tempCircMod: String;
- @Input() isModifyingStatus: Boolean;
- @Input() isModifyingCircMod: Boolean;
- @Input() isModifyingCallNumber: Boolean;
- @Input() isModifyingLocation: Boolean;
- // Users Tab
- @Input() userBarcode: String;
- @Input() userRoleInput: String;
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
) {
- this.materialsDataSource = new GridDataSource();
}
ngOnInit() {
this.course.getCourses([this.courseId]).then(course => {
this.currentCourse = course[0];
});
- this.materialsDataSource.getRows = (pager: Pager, sort: any[]) => {
- return this.loadMaterialsGrid(pager);
- }
}
// Edit Tab
}
// Materials Tab
- loadMaterialsGrid(pager: Pager): Observable<any> {
- return new Observable<any>(observer => {
- this.course.getMaterials(this.courseId).then(materials => {
- materials.forEach(material => {
- this.course.fleshMaterial(material.item(), material.relationship()).then(fleshed_material => {
- this.materialsDataSource.data.push(fleshed_material);
- });
- });
- });
- observer.complete();
- });
- }
- associateItem(barcode, relationship) {
- if (barcode) {
- let args = {
- barcode: barcode,
- relationship: relationship,
- isModifyingCallNumber: this.isModifyingCallNumber,
- isModifyingCircMod: this.isModifyingCircMod,
- isModifyingLocation: this.isModifyingLocation,
- isModifyingStatus: this.isModifyingStatus,
- tempCircMod: this.tempCircMod,
- tempLocation: this.tempLocation,
- tempStatus: this.tempStatus,
- currentCourse: this.currentCourse
- }
- this.barcodeInput = null;
-
- this.pcrud.search('acp', {barcode: args.barcode}, {
- flesh: 3, flesh_fields: {acp: ['call_number']}
- }).subscribe(item => {
- let associatedMaterial = this.course.associateMaterials(item, args);
- associatedMaterial.material.then(res => {
- item = associatedMaterial.item;
- let new_cn = item.call_number().label();
- if (this.tempCallNumber) new_cn = this.tempCallNumber;
- this.course.updateItem(item, this.currentCourse.owning_lib(),
- new_cn, args.isModifyingCallNumber
- ).then(resp => {
- this.course.fleshMaterial(item.id(), args.relationship).then(fleshed_material => {
- this.materialsDataSource.data.push(fleshed_material);
- });
- if (item.circ_lib() != this.currentCourse.owning_lib()) {
- this.materialAddDifferentLibraryString.current()
- .then(str => this.toast.warning(str));
- } else {
- this.materialAddSuccessString.current()
- .then(str => this.toast.success(str));
- }
- });
- }, err => {
- this.materialAddFailedString.current()
- .then(str => this.toast.danger(str));
- });
- });
- }
- }
-
- deleteSelected(items) {
- let item_ids = [];
- items.forEach(item => {
- this.materialsDataSource.data.splice(this.materialsDataSource.data.indexOf(item, 0), 1);
- item_ids.push(item.id())
- });
- this.pcrud.search('acmcm', {course: this.courseId, item: item_ids}).subscribe(material => {
- material.isdeleted(true);
- this.pcrud.autoApply(material).subscribe(
- val => {
- this.course.resetItemFields(material, this.currentCourse.owning_lib());
- console.debug('deleted: ' + val);
- this.materialDeleteSuccessString.current().then(str => this.toast.success(str));
- },
- err => {
- this.materialDeleteFailedString.current()
- .then(str => this.toast.danger(str));
- }
- );
- });
- }
}
\ No newline at end of file
}
}
- fleshMaterial(itemId, relationship?): Promise<any> {
+ getUsers(course_ids?: Number[]): Promise<IdlObject[]> {
+ if (!course_ids) {
+ return this.pcrud.retrieveAll('acmcu',
+ {}, {atomic: true}).toPromise();
+ } else {
+ return this.pcrud.search('acmcu', {course: course_ids},
+ {}, {atomic: true}).toPromise();
+ }
+ }
+
+ 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(), itemId
+ 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()));
- if (relationship) item._relationship = relationship;
+ item._id = material.id();
+ if (material.relationship())
+ item._relationship = material.relationship();
}
}, err => {
reject(err);
});
}
+ 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 = [];
return new Promise((resolve, reject) => {
return response;
}
+ 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);
+ new_user.course(args.currentCourse.id());
+ new_user.usr(patron_id);
+ return this.pcrud.create(new_user).toPromise()
+ }
+
disassociateMaterials(courses) {
return new Promise((resolve, reject) => {
let course_ids = [];
this.pcrud.search('acmcu', {user: user_ids}).subscribe(user => {
user.course(user_ids);
this.pcrud.autoApply(user).subscribe(res => {
- console.log(res);
+ console.debug(res);
}, err => {
reject(err);
}, () => {
@params args : Supplied object to filter search.
/);
-__PACKAGE__->register_method(
- method => 'fetch_courses',
- autoritative => 1,
- api_name => 'open-ils.circ.courses.retrieve',
- signature => q/
- Returns an array of course materials.
- @params course_id: The id of the course we want to retrieve
- /);
-
sub fetch_course_materials {
my ($self, $conn, $args) = @_;
my $e = new_editor();
return $targets;
}
+__PACKAGE__->register_method(
+ method => 'fetch_courses',
+ autoritative => 1,
+ api_name => 'open-ils.circ.courses.retrieve',
+ signature => q/
+ Returns an array of course materials.
+ @params course_id: The id of the course we want to retrieve
+ /);
+
sub fetch_courses {
my ($self, $conn, @course_ids) = @_;
my $e = new_editor();
unless ($self->api_name =~ /\.staff/) and $e->allowed('MANAGE_RESERVES');
- $users->{list} = $e->search_asset_course_module_course_users($filter);
+ $users->{list} = $e->search_asset_course_module_course_users($filter, {order_by => {acmcu => 'id'}});
for my $course_user (@{$users->{list}}) {
my $patron = {};
$patron->{id} = $course_user->id;
my $final_user = {};
$final_user->{id} = $user->{id};
$final_user->{usr_role} = $user->{usr_role};
+ $final_user->{patron_id} = $user->{patron_data}->id;
$final_user->{first_given_name} = $user->{patron_data}->first_given_name;
+ $final_user->{second_given_name} = $user->{patron_data}->second_given_name;
$final_user->{family_name} = $user->{patron_data}->family_name;
$final_user->{pref_first_given_name} = $user->{patron_data}->pref_first_given_name;
$final_user->{pref_family_name} = $user->{patron_data}->pref_family_name;
+ $final_user->{pref_second_given_name} = $user->{patron_data}->pref_second_given_name;
+ $final_user->{pref_suffix} = $user->{patron_data}->pref_suffix;
+ $final_user->{pref_prefix} = $user->{patron_data}->pref_prefix;
push @$targets, $final_user;
}
return $self->load_simple("home") if $path =~ m|opac/home|;
return $self->load_simple("css") if $path =~ m|opac/css|;
+ return $self->load_cresults if $path =~ m|opac/course/results|;
+ return $self->load_simple("course_search") if $path =~ m|opac/course_search|;
return $self->load_simple("advanced") if
$path =~ m:opac/(advanced|numeric|expert):;
{course => $course_id}
);
return Apache2::Const::OK;
+}
+
+sub load_cresults {
+ my $self = shift;
+ my %args = @_;
+ my $internal = $args{internal};
+ my $cgi = $self->cgi;
+ my $ctx = $self->ctx;
+ my $e = $self->editor;
+ my $limit = 10;
+
+ $ctx->{page} = 'cresult' unless $internal;
+ $ctx->{ids} = [];
+ $ctx->{courses} = [];
+ $ctx->{hit_count} = 0;
+ $ctx->{search_ou} = $self->_get_search_lib();
+ my $page = $cgi->param('page') || 0;
+ my $offset = $page * $limit;
+ my $results;
+ $ctx->{page_size} = $limit;
+ $ctx->{search_page} = $page;
+ $ctx->{pagable_limit} = 50;
+
+ # fetch this page plus the first hit from the next page
+ if ($internal) {
+ $limit = $offset + $limit + 1;
+ $offset = 0;
+ }
+
+ my ($user_query, $query, @queries, $modifiers) = _prepare_course_search($cgi, $ctx);
+
+ return Apache2::Const::OK unless $query;
+
+ $ctx->{user_query} = $user_query;
+ $ctx->{processed_search_query} = $query;
+ my $search_args = {};
+ my $course_numbers = ();
+
+ my $where_clause;
+ my $and_terms = [];
+ my $or_terms = [];
+
+ # Handle is_archived checkbox and Org Selector
+ my $search_orgs = $U->get_org_descendants($ctx->{search_ou});
+ push @$and_terms, {'owning_lib' => $search_orgs};
+ push @$and_terms, {'-not' => {'+acmc' => 'is_archived'}} unless $query =~ qr\#include_archived\;
+
+ # Now let's push the actual queries
+ for my $query_obj (@queries) {
+ my $type = $query_obj->{'qtype'};
+ my $query = $query_obj->{'value'};
+ my $bool = $query_obj->{'bool'};
+ my $contains = $query_obj->{'contains'};
+ my $operator = ($contains eq 'nocontains') ? '!~*' : '~*';
+ my $search_query;
+ if ($type eq 'instructor') {
+ my $in = ($contains eq 'nocontains') ? "not in" : "in";
+ $search_query = {'id' => {$in => {
+ 'from' => 'acmcu',
+ 'select' => {'acmcu' => ['course']},
+ 'where' => {'usr' => {'in' => {
+ 'from' => 'au',
+ 'select' => {'au' => ['id']},
+ 'where' => {
+ '-or' => [
+ {'pref_first_given_name' => {'~*' => $query}},
+ {'first_given_name' => {'~*' => $query}},
+ {'pref_second_given_name' => {'~*' => $query}},
+ {'second_given_name' => {'~*' => $query}},
+ {'pref_family_name' => {'~*' => $query}},
+ {'family_name' => {'~*' => $query}}
+ ]
+ }
+ }}}
+ }}};
+ } else {
+ $search_query = ($contains eq 'nocontains') ?
+ {'+acmc' => { $type => {$operator => $query}}} :
+ {$type => {$operator => $query}};
+ }
+
+ if ($bool eq 'or') {
+ push @$or_terms, $search_query;
+ }
+
+ if ($bool eq 'and') {
+ push @$and_terms, $search_query;
+ }
+ }
+
+ if ($or_terms and @$or_terms > 0) {
+ if ($and_terms and @$and_terms > 0) {
+ push @$or_terms, $and_terms;
+ }
+ $where_clause = {'-or' => $or_terms};
+ } else {
+ $where_clause = {'-and' => $and_terms};
+ }
+
+ my $hits = $e->json_query({
+ "from" => "acmc",
+ "select" => {"acmc" => ['id']},
+ "where" => $where_clause
+ });
+
+ my $results = $e->json_query({
+ "from" => "acmc",
+ "select" => {"acmc" => [
+ 'id',
+ 'name',
+ 'course_number',
+ 'section_number',
+ 'is_archived',
+ 'owning_lib'
+ ]},
+ "limit" => $limit,
+ "offset" => $offset,
+ "order_by" => {"acmc" => ['id']},
+ "where" => $where_clause
+ });
+ for my $result (@$results) {
+ push @{$ctx->{courses}}, {
+ id => $result->{id},
+ course_number => $result->{course_number},
+ section_number => $result->{section_number},
+ owning_lib => $result->{owning_lib},
+ name => $result->{name},
+ is_archived => $result->{is_archived},
+ instructors => []
+ }
+ }
+
+ #$ctx->{courses} = $@courses;#[{id=>10, name=>"test", course_number=>"LIT"}];
+ $ctx->{hit_count} = @$hits || 0;
+ #$ctx->{hit_count} = 0;
+ return Apache2::Const::OK;
+}
+
+sub _prepare_course_search {
+ my ($cgi, $ctx) = @_;
+
+ my ($user_query, @queries) = _prepare_query($cgi);
+ my $modifiers;
+ $user_query //= '';
+
+ my $query = $user_query;
+ $query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter};
+
+ foreach ($cgi->param('modifier')) {
+ $query = ('#' . $_ . ' ' . $query) unless $query =~ qr/\#\Q$_/;
+
+ }
+ # filters
+ foreach (grep /^fi:/, $cgi->param) {
+ /:(-?\w+)$/ or next;
+ my $term = join(",", $cgi->param($_));
+ $query .= " $1($term)" if length $term;
+ }
+
+ return () unless $query;
+
+ return ($user_query, $query, @queries);
+}
+
+sub _prepare_query {
+ my $cgi = shift;
+
+ return $cgi->param('query') unless $cgi->param('qtype');
+
+ my %parts;
+ my @part_names = qw/qtype contains query bool modifier/;
+ $parts{$_} = [ $cgi->param($_) ] for (@part_names);
+
+ my $full_query = '';
+ my @queries;
+ for (my $i = 0; $i < scalar @{$parts{'qtype'}}; $i++) {
+ my ($qtype, $contains, $query, $bool, $modifier) = map { $parts{$_}->[$i] } @part_names;
+ next unless $query =~ /\S/;
+
+ $contains = "" unless defined $contains;
+
+ push @queries, {
+ contains => $contains,
+ bool => $bool,
+ qtype => $qtype,
+ value => $query
+ };
+
+ $bool = ($bool and $bool eq 'or') ? '||' : '&&';
+
+ $query = "$qtype:$query";
+
+ $full_query = $full_query ? "($full_query $bool $query)" : $query;
+ }
+
+ return ($full_query, @queries);
}
\ No newline at end of file
--- /dev/null
+[%- PROCESS "opac/parts/header.tt2";
+ WRAPPER "opac/parts/base.tt2";
+ INCLUDE "opac/parts/topnav.tt2";
+ ctx.page_title = l("Course Search Results");
+ page = CGI.param('page');
+ page = page.match('^\d+$') ? page : 0; # verify page is a sane value
+
+ page_count = (!ctx.page_size.defined || !ctx.hit_count.defined || ctx.page_size == 0) ? 1 : POSIX.ceil(ctx.hit_count / ctx.page_size);
+
+ # We don't want search engines indexing search results
+ ctx.metalinks.push('<meta name="robots" content="noindex,follow">');
+
+ PROCESS "opac/parts/misc_util.tt2";
+ PROCESS get_library;
+ ctx.result_start = 1 + ctx.page_size * page;
+ ctx.result_stop = ctx.page_size * (page + 1);
+ IF ctx.result_stop > ctx.hit_count; ctx.result_stop = ctx.hit_count; END;
+
+ result_count = ctx.result_start;
+-%]
+<h2 class="sr-only">[% l('Course Search Results') %]</h2>
+[% INCLUDE "opac/parts/searchbar.tt2" %]
+<div class="almost-content-wrapper">
+ <div id="results_header_bar">
+ <div id="results_header_inner">
+ <div class="results_header_btns">
+ <a href="[% mkurl(ctx.opac_root _ '/course_search', {$loc_name => loc_value}, 1) %]">[% l('Another Search') %]</a>
+ </div>
+ </div>
+ </div>
+</div>
+<div id="content-wrapper">
+ <div id="main-content">
+ <div id="results-page">
+ [% PROCESS "opac/parts/result/paginate.tt2" %]
+ [% ctx.results_count_header = PROCESS results_count_header;
+ ctx.results_count_header %]
+ <div id="result_table_div">
+ <div id="result_block" class="result_block_visible">
+
+ <table id="result_table_table" title="[% l('Search Results') %]"
+ class="table_no_border_space table_no_cell_pad">
+ <thead class="sr-only">
+ <tr>
+ <th>[% l('Search result number') %]</th>
+ <th>[% l('Course details') %]</th>
+ </tr>
+ </thead>
+ <tbody id="result_table">
+ [% FOR course IN ctx.courses %]
+ [% course_url_path = ctx.opac_root _ '/course/' _ course.id; %]
+ [% # Do not pass "advanced params" to result detail code.
+ # Instead, pass the scrubed query in one-line form
+ del_parms = del_parms.merge(['query', 'bool',
+ 'qtype', 'contains', '_adv']);
+ add_parms.import(
+ {query => ctx.naive_query_scrub(ctx.user_query)} );
+ %]
+ <tr class="result_table_row">
+ <td class="results_row_count" name="results_row_count">
+ [% result_count; result_count = result_count + 1 %].
+ </td>
+ <td class="result_table_pic_header"></td>
+ <td class="result_table_title_cell" name="result_table_title_cell">
+ <div class="result_metadata">
+ <a class="record_title search_link" name="course_[% course.id %]"
+ href="[% mkurl(course_url_path) %]"
+ [% html_text_attr('title', l('Display course details for "[_1]"', course.name)) %]>
+ [% course.course_number %]: [% l(course.name) %]
+ </a>
+ <div>
+ [% FOR instructor IN course.instructors %]
+ [% instructorString = '';
+ IF instructor.pref_family_name;
+ instructorString = instructorString _ instructor.pref_family_name _ ', ';
+ ELSE;
+ instructorString = instructorString _ instructor.family_name _ ', ';
+ END;
+ IF instructor.pref_first_given_name;
+ instructorString = instructorString _ instructor.pref_first_given_name;
+ ELSE;
+ instructorString = instructorString _ instructor.first_given_name;
+ END; %]
+ <a title="[% l('Perform an Instructor Search') %]"
+ class="record_author"
+ href="[%
+ mkurl(ctx.opac_root _ '/results', {qtype => 'instructor', query => instructorString})
+ %]" rel="nofollow" vocab="">
+ [% instructorString %] ([% l(instructor.usr_role) %])</a>.
+ [% END %]
+ </div>
+ <div>
+ <span><strong>[% l('Course Number') %]</strong>: [% course.course_number %]</span>
+ </div>
+ <div>
+ <span><strong>[% l('Section Number') %]</strong>: [% course.section_number %]</span>
+ </div>
+ </div>
+ </td>
+ <td>
+ [% ctx.get_aou(course.owning_lib).name %]
+ </td>
+ </tr>
+ [% END %]
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ <div class="common-full-pad"></div>
+ </div>
+ <br class="clear-both" />
+</div>
+[%- END %]
--- /dev/null
+[%- PROCESS "opac/parts/header.tt2";
+ WRAPPER "opac/parts/base.tt2";
+ INCLUDE "opac/parts/topnav.tt2";
+ ctx.page_title = l("Course Search");
+
+ PROCESS "opac/parts/misc_util.tt2";
+ PROCESS get_library;
+ ctx.metalinks.push('<meta name="robots" content="noindex,follow">');
+-%]
+
+<h2 class="sr-only">[% l('Course Search') %]</h2>
+
+<div id="search-wrapper">
+ <div id="search-box">
+ <span class="search_catalog_lbl mobile_hide">[% l('Search the Catalog') %]</span>
+ <span class="adv_search_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/home') %]"
+ id="home_adv_search_link">[%l('Basic Search')%]</a></span>
+ <span class="adv_search_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/advanced', {}, depart_list) %]"
+ id="home_adv_search_link">[%l('Advanced Search')%]</a></span>
+ <span class="browse_the_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/browse') %]">[%
+ l('Browse the Catalog')%]</a></span>
+ [% INCLUDE 'opac/parts/cart.tt2' %]
+ </div>
+</div>
+
+<div id="content-wrapper">
+ <div id="main-content">
+ <form action="[% ctx.opac_root %]/course/results" method="get">
+ <!-- TODO: Refactor simple.js addSearchRow to better handle
+ tables with IDs that aren't adv_... -->
+ <div class="header_middle" id="adv_search_input">
+ [% l("Course Search Input") %]
+ </div>
+
+ <div id="adv_search_filters" class="adv_filter_block">
+ <div class="adv_filter_block_item">
+ <div>
+ <strong><label for="ord.id">[% l('Search Library') %]</label></strong>
+ </div>
+ <div>
+ [% PROCESS "opac/parts/org_selector.tt2" %]
+ [% INCLUDE build_org_selector show_loc_groups=1 id=org.id %]
+ <span class="course_search_archived">
+ <input type="checkbox" name="modifier" value="include_archived"
+ [% CGI.param('modifier').grep('include_archived').size ? ' checked="checked"' : '' %]
+ id="opac.course_result.include_archived" />
+ <label for="opac.course_result.include_archived">
+ [% l('Include Archived Courses?') %]
+ </label>
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <div class="advanced_div">
+ <div id='adv_global_search' class='data_grid data_grid_center'>
+ <div id='adv_search_rows'>
+ <div class='adv_global_input_container'>
+ <table id="adv_global_input_table" role="presentation">
+ <tbody id='adv_global_tbody'>
+ [% INCLUDE "opac/parts/course_search/global_row.tt2" %]
+ <!-- add a new row -->
+ <tr id="adv_global_addrow">
+ <td class="td-search-left">
+ <a href="javascript:;" id="myopac_new_global_row"
+ onclick='addSearchRow();'>
+ [% l('Add Search Row') %]
+ </a>
+ </td>
+ </tr>
+ <tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+
+ <div id='course_search_submit'>
+ <input type="hidden" name="_course" value="1" />
+ <span>
+ <input id='search-submit-go' type="submit" value="[% l('Search') %]" title="[% l('Search') %]" class="opac-button"
+ onclick='setTimeout(function(){$("search-submit-spinner").className=""; $("search-submit-go").className="hidden"}, 2000)'/>
+ <img id='search-submit-spinner' src='/opac/images/progressbar_green.gif[% ctx.cache_key %]'
+ class='hidden' alt="[% l('Search in progress icon') %]"/>
+ </span>
+ <a href="[% mkurl(ctx.opac_root _ '/course_search', {$loc_name => loc_value}, 1) %]"
+ class="opac-button">[% l('Clear Form') %]</a>
+ </div>
+ </div>
+ </div>
+ <div class="common-full-pad"></div>
+ </form>
+ </div>
+</div>
+
+[% END %]
\ No newline at end of file
font-size: [% css_fonts.size_bigger %];
}
+.search_courses_label {
+ font-size: [% css_fonts.size_bigger %];
+}
+
.lbl1 {
font-size: [% css_fonts.size_bigger %];
font-weight:bold;
--- /dev/null
+[%
+ contains_options = [
+ {value => 'contains', label => l('Contains')},
+ {value => 'nocontains', label => l('Does not contain')}
+ ];
+ contains = CGI.param('contains');
+ queries = CGI.param('query');
+ bools = CGI.param('bool');
+ qtypes = CGI.param('qtype');
+ rowcount = 3;
+
+ # scalar.merge treats the scalar as a 1-item array
+ WHILE queries.size < rowcount; queries = queries.merge(['']); END;
+ WHILE bools.size < rowcount; bools = bools.merge(['and']); END;
+ WHILE qtypes.size < rowcount; qtypes = qtypes.merge(search.default_qtypes.${qtypes.size} ? [search.default_qtypes.${qtypes.size}] : ['keyword']); END;
+
+ FOR qtype IN qtypes;
+ c = contains.shift;
+ b = bools.shift;
+ q = queries.shift; %]
+
+<!-- tag the second row so the bool column won't be hidden -->
+<tr[% IF loop.index == 1 %] id="adv_global_row"[% END %]>
+ <td class="td-left">
+
+ <!-- bool selector. hide for first row. safe to ignore first bool value in form submission -->
+ <select title="[% l('Boolean search operator') %]"
+ name='bool' style='width: auto' [% IF loop.first %] class='invisible' [% END %]>
+ <option value='and' [% b == 'and' ? 'selected="selected"' : '' %]>[% l('And') %]</option>
+ <option value='or' [% b == 'or' ? 'selected="selected"' : '' %]>[% l('Or') %]</option>
+ </select>
+
+ <!-- keyword, subject, etc. selector -->
+ <span class="qtype_selector_margin">
+ [% INCLUDE "opac/parts/course_search/qtype_selector.tt2"
+ query_type=qtype %]
+ </span>
+
+ <select title="[% l('Search phrase match strictness') %]"
+ name='contains' style='margin-right: 7px;'>
+ [% FOR o IN contains_options; -%]
+ <option value="[% o.value %]" [% c == o.value ? ' selected="selected"' : '' %]>[% o.label %]</option>
+ [% END %]
+ </select>
+ <input title="[% l('Search term') %]" aria-label="[% l('Search term') %]"
+ type='text' size='18' name='query' value="[% q | html %]" x-webkit-speech [% IF loop.index == 0 %] autofocus [% END %] />
+ <a href="javascript:;" class="row-remover"
+ title="[% l('Remove row') %]" alt="[% l('Remove row') %]"
+ onclick='return killRowIfAtLeast(2, this);'>
+ <img src="[% ctx.media_prefix %]/images/expert_row_close_btn.png[% ctx.cache_key %]"
+ alt="[% l('Remove row') %]"/>
+ </a>
+ </td>
+</tr>
+[% END %]
--- /dev/null
+[% query_types = [
+ {value => "name", label => l("Title"), plural_label => l("Titles"), browse => 1},
+ {value => "instructor", label => l("Instructor"), plural_label => l('Instructors')},
+ {value => "course_number", label => l("Course Number")}
+];
+-%]
+<select name="[% name || 'qtype' %]"[% IF id; ' id="'; id ; '"' ; END -%]
+ title="[% l('Select query type:') %]">
+ [% query_type = query_type || CGI.param('qtype') || search.default_qtypes.0;
+ FOR qt IN query_types;
+ NEXT IF browse_only AND NOT qt.browse -%]
+ <option value='[% qt.value | html %]'[%
+ query_type == qt.value ? ' selected="selected"' : ''
+ %]>[% IF plural AND qt.plural_label;
+ qt.plural_label | html;
+ ELSE;
+ qt.label | html;
+ END %]</option>
+ [% END -%]
+</select>
[% IF copy_info.courses.size > 0 || ctx.is_staff %]
<td>[% copy_info.circ_modifier | html %]</td>
- [% ELSIF ctx.is_staff %]
+ [% ELSIF ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %]
+ <td></td>
+ [% END %]
+ [% IF ctx.is_staff %]
<td>
[% copy_info.age_protect ?
ctx.get_crahp(copy_info.age_protect).name : l('None') | html %]
'-';
END;
%]</td>
- [% ELSIF ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %]
- <td></td>
[% END # is_staff %]
[% IF ctx.is_staff OR serial_holdings %]
<td>[% # Show copy/volume hold links to staff (without
<span class="adv_search_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/advanced', {}, expert_search_parms.merge(browse_search_parms, facet_search_parms)) %]"
id="home_adv_search_link">[% l('Advanced Search') %]</a></span>
<span class="browse_the_catalog_lbl"><a href="[% mkurl(ctx.opac_root _ '/browse', {}, expert_search_parms.merge(general_search_parms, facet_search_parms, ['fi:has_browse_entry'])) %]">[% l('Browse the Catalog') %]</a></span>
+ [% IF ctx.get_org_setting(ctx.aou_tree.id, 'circ.course_materials_opt_in') == 1 %]
+ <span class="search_courses_label"><a href="[% mkurl(ctx.opac_root _ '/course_search') %]">[% l('Search for Courses') %]</a></span>
+ [% END %]
[% INCLUDE 'opac/parts/cart.tt2' %]
</div>
<div class="searchbar [% is_home_page ? 'searchbar-home' : '' %]">
--- /dev/null
+<div class="col-md-12" ng-show="!has_course_perms">
+ <div class="alert alert-danger">
+ [% l('You do not have sufficient permissions to view this page') %]
+ </div>
+</div>
+
+<div ng-show="has_course_perms">
+ <div class="col-md-6" ng-show="!courses.length">
+ <div class="alert alert-info">
+ [% l('No Associated Courses') %]
+ </div>
+ </div>
+ <div class="col-md-6" ng-show="courses.length">
+ <div class="flex-row">
+ <div class="flex-cell flex-2 strong-text-2">
+ [% l('Associated Courses') %]
+ </div>
+ </div>
+
+ <div class="flex-row well well-sm" ng-repeat="course in courses">
+ <div class="flex-cell">
+ <a href="/eg2/staff/admin/local/asset/course_list/{{course.id()}}">{{course.course_number()}}: {{course.name()}}</a>
+ </div>
+ </div>
+</div>
+
+ <div class="col-md-6" ng-show="!instructors_exist">
+ <div class="alert alert-info">
+ [% l('No Associated Instructors') %]
+ </div>
+ </div>
+ <div class="col-md-6" ng-show="instructors_exist">
+ <div class="flex-row">
+ <div class="flex-cell flex-2 strong-text-2">
+ [% l('Associated Instructors') %]
+ </div>
+ </div>
+
+ <div class="flex-row" ng-repeat="(key, instructor) in instructors">
+ <div class="flex-cell">
+ <strong>
+ <span ng-if="instructor.pref_family_name">
+ {{instructor.pref_family_name}},
+ </span>
+ <span ng-if="!instructor.pref_family_name">
+ {{instructor.family_name}},
+ </span>
+ <span ng-if="instructor.pref_first_given_name">
+ {{instructor.pref_first_given_name}}
+ </span>
+ <span ng-if="!instructor.pref_first_given_name">
+ {{instructor.first_given_name}}
+ </span>
+ <span ng-if="instructor.pref_second_given_name">
+ {{instructor.pref_second_given_name}}
+ </span>
+ <span ng-if="!instructor.pref_second_given_name && instructor.second_given_name">
+ {{instructor.second_given_name}}
+ </span>
+ </strong>
+ <div class="well well-sm dt">
+ <div class="flex-row" ng-repeat="key in instructor._linked_course">
+ <div class="flex-cell">
+ <span>{{key.course}} </span>
+ <span>({{key.role}})</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
<li ng-class="{active : tab == 'triggered_events'}">
<a href="./cat/item/{{copy.id()}}/triggered_events">[% l('Triggered Events') %]</a>
</li>
+ <li ng-class="{active : tab == 'course'}" ng-if="has_course_perms && courseModulesOptIn">
+ <a href="./cat/item/{{copy.id()}}/course">[% l('Course Info') %]</a>
+ </li>
</ul>
<div class="tab-content">
<div class="tab-pane active">
* Detail view -- shows one copy
*/
.controller('ViewCtrl',
- ['$scope','$q','$location','$routeParams','$timeout','$window','egCore','egItem','egBilling','egCirc',
-function($scope , $q , $location , $routeParams , $timeout , $window , egCore , itemSvc , egBilling , egCirc) {
+ ['$scope','$q','egGridDataProvider','$location','$routeParams','$timeout','$window','egCore','egItem','egBilling','egCirc',
+function($scope , $q , egGridDataProvider , $location , $routeParams , $timeout , $window , egCore , itemSvc , egBilling , egCirc) {
var copyId = $routeParams.id;
$scope.args.copyId = copyId;
$scope.tab = $routeParams.tab || 'summary';
$scope.context.page = 'detail';
$scope.summaryRecord = null;
-
+ $scope.courseModulesOptIn = fetchCourseOptIn();
+ $scope.has_course_perms = fetchCoursePerms();
$scope.edit = false;
if ($scope.tab == 'edit') {
$scope.tab = 'summary';
$scope.edit = true;
}
-
// use the cached record info
if (itemSvc.copy) {
$scope.copy_alert_count = itemSvc.copy.copy_alerts().filter(function(aca) {
});
}
+ // Check for Course Modules Opt-In to enable Course Info tab
+ function fetchCourseOptIn() {
+ return egCore.org.settings(
+ 'circ.course_materials_opt_in'
+ ).then(function(set) {
+ $scope.courseModulesOptIn = set['circ.course_materials_opt_in'];
+
+ return $scope.courseModulesOptIn;
+ });
+ }
+
+ function fetchCoursePerms() {
+ return egCore.perm.hasPermAt('MANAGE RESERVES', true).then(function(orgIds) {
+ if(orgIds.indexOf(egCore.auth.user().ws_ou()) != -1){
+ $scope.has_course_perms = true;
+
+ return $scope.has_course_perms;
+ }
+ });
+ }
+
$scope.addBilling = function(circ) {
egBilling.showBillDialog({
xact_id : circ.id(),
})
}
+ function loadCourseInfo() {
+ delete $scope.courses;
+ delete $scope.instructors;
+ delete $scope.course_ids;
+ delete $scope.instructors_exist;
+ if (!copyId) return;
+ $scope.course_ids = [];
+ $scope.courses = [];
+ $scope.instructors = {};
+
+ egCore.pcrud.search('acmcm', {
+ item: copyId
+ }, {
+ flesh: 3,
+ flesh_fields: {
+ acmcm: ['course']
+ }, order_by: {acmc : 'id desc'}
+ }).then(null, null, function(material) {
+
+ $scope.courses.push(material.course());
+ egCore.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.course_users.retrieve',
+ material.course().id()
+ ).then(null, null, function(instructors) {
+ angular.forEach(instructors, function(instructor) {
+ var patron_id = instructor.patron_id.toString();
+ if (!$scope.instructors[patron_id]) {
+ $scope.instructors[patron_id] = instructor;
+ $scope.instructors_exist = true;
+ $scope.instructors[patron_id]._linked_course = [];
+ }
+ $scope.instructors[patron_id]._linked_course.push({
+ role: instructor.usr_role,
+ course: material.course().name()
+ });
+ });
+ });
+ });
+ }
+
// we don't need all data on all tabs, so fetch what's needed when needed.
function loadTabData() {
loadMostRecentTransit();
break;
+ case 'course':
+ loadCourseInfo();
+ break;
+
case 'triggered_events':
var url = $location.absUrl().replace(/\/staff.*/, '/actor/user/event_log');
url += '?copy_id=' + encodeURIComponent(copyId);