-<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}}
- [preloadLinkedValues]="true"
- hiddenFields="is_archived,id">
-</eg-fm-record-editor>
+</div>
\ 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)="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="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-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 col-md-6 mt-3 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 class="col-lg-12 col-md-6 mt-3 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-sm-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="col-sm-6 text-right">
- <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-lg-8 mt-3">
- <eg-grid #usersGrid [dataSource]="usersDataSource">
- <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelectedUsers($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="_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="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>
- </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>
-<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 #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
- users: any[] = [];
- @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;
- usersDataSource: GridDataSource;
- @Input() userBarcode: String;
- @Input() userRoleInput: String;
- @Input() isPublicRole: Boolean;
constructor(
private auth: AuthService,
private course: CourseService,
private route: ActivatedRoute,
private toast: ToastService
) {
- this.materialsDataSource = new GridDataSource();
- this.usersDataSource = new GridDataSource();
}
ngOnInit() {
this.course.getCourses([this.courseId]).then(course => {
this.currentCourse = course[0];
});
- this.usersDataSource.getRows = (pager: Pager, sort: any[]) => {
- return this.loadUsersGrid(pager);
- }
- 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));
- });
- });
- }
- }
-
- deleteSelectedMaterials(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));
- }
- );
- });
- }
-
- // Users Tab
- loadUsersGrid(pager: Pager): Observable<any> {
- return new Observable<any>(observer => {
- this.course.getUsers(this.courseId).then(users => {
- users.forEach(user => {
- console.log(user);
- this.course.fleshUser(user.usr(), user.usr_role(), user.is_public()).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(
- patron, args.role, args.is_public
- ).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));
- });
- });
- }
- }
-
- 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
}
}
- fleshMaterial(itemId, relationship?): Promise<any> {
+ 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(patron_id, role?, is_public?): Promise<any> {
+ 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(), patron_id
+ this.auth.token(), course_user.usr()
).subscribe(patron => {
user = patron;
- if (role) user._role = role
- if (is_public) user._is_public = is_public
+ 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));
}
associateUsers(patron_id, args) {
- console.log(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);
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);
}, () => {