LP1849212 Associtate and Disassociate Course With Instructors
authorZavier Banks <zbanks@catalyte.io>
Mon, 9 Dec 2019 16:52:39 +0000 (16:52 +0000)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 14 Sep 2020 22:16:53 +0000 (18:16 -0400)
Creating a component that associates and disassociates instructors with
courses using the course list.

Signed-off-by: Zavier Banks <zbanks@catalyte.io>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-list.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-reserves.module.ts
Open-ILS/src/eg2/src/app/staff/share/course.service.ts

index b96a83d..db6946f 100644 (file)
@@ -3165,7 +3165,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
     </class>
-    <class id="acmcm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_course_materials" oils_persist:tablename="asset.course_module_course_materials" reporter:label="Course Materials">
+    <class id="acmcm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::course_module_course_materials" oils_persist:tablename="asset.course_module_course_materials" reporter:label="Course Materials" oils_persist:field_safe="true">
         <fields oils_persist:primary="id" oils_persist:sequence="asset.course_module_course_materials_id_seq">
             <field reporter:label="ID" name="id" reporter:datatype="id" />
             <field reporter:label="Course" name="course" reporter:datatype="link" />
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.html b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.html
new file mode 100644 (file)
index 0000000..079acd7
--- /dev/null
@@ -0,0 +1,57 @@
+<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>
+
+<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">&times;</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>
+          <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>
+          </div>
+            <input type="text" [(ngModel)]="new_usr" />
+        </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)">
+        </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="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>
\ No newline at end of file
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/course-reserves/course-associate-users.component.ts
new file mode 100644 (file)
index 0000000..b3be2c4
--- /dev/null
@@ -0,0 +1,102 @@
+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
+}
\ No newline at end of file
index 2dd917f..d7312a9 100644 (file)
 
 <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"
@@ -26,6 +30,8 @@
     </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>
 </div>
 
index 91c4b28..ebde776 100644 (file)
@@ -16,6 +16,9 @@ import {ToastService} from '@eg/share/toast/toast.service';
 import {CourseAssociateMaterialComponent
     } from './course-associate-material.component';
 
+import {CourseAssociateUsersComponent
+    } from './course-associate-users.component';
+
 @Component({
     templateUrl: './course-list.component.html'
 })
@@ -34,6 +37,9 @@ export class CourseListComponent implements OnInit {
     @ViewChild('archiveSuccessString', { static: true }) archiveSuccessString: StringComponent;
     @ViewChild('courseMaterialDialog', {static: true})
         private courseMaterialDialog: CourseAssociateMaterialComponent;
+    @ViewChild('courseUserDialog', {static: true})
+        private courseUserDialog: CourseAssociateUsersComponent;
+
     @Input() sort_field: string;
     @Input() idl_class = "acmc";
     @Input() dialog_size: 'sm' | 'lg' = 'lg';
@@ -79,7 +85,7 @@ export class CourseListComponent implements OnInit {
                 limit: pager.limit,
                 order_by: orderBy
             };
-            return this.pcrud.retrieveAll(this.idl_class, searchOps, {fleshSelectors: true});
+            return this.pcrud.retrieveAll(this.idl_class, searchOps, {fleshSelectors: true})
         };
     }
 
@@ -187,6 +193,41 @@ export class CourseListComponent implements OnInit {
         });
     }
 
+    /**
+     * 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(
@@ -219,5 +260,21 @@ export class CourseListComponent implements OnInit {
             });
         });
     }
+
+    /**
+     * 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);
+            });
+        });
+    }
 }
 
index 180faca..3823648 100644 (file)
@@ -5,6 +5,7 @@ import {AdminCommonModule} from '@eg/staff/admin/common.module';
 import {CourseListComponent} from './course-list.component';
 import {CoursePageComponent} from './course-page.component';
 import {CourseAssociateMaterialComponent} from './course-associate-material.component';
+import {CourseAssociateUsersComponent} from './course-associate-users.component';
 import {CourseReservesRoutingModule} from './routing.module';
 import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
 
@@ -12,7 +13,8 @@ import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-loca
   declarations: [
     CourseListComponent,
     CoursePageComponent,
-    CourseAssociateMaterialComponent
+    CourseAssociateMaterialComponent,
+    CourseAssociateUsersComponent
   ],
   imports: [
     StaffCommonModule,
index edeeed3..de946c4 100644 (file)
@@ -170,6 +170,31 @@ export class CourseService {
         });
     }
 
+    disassociateUsers(user) {
+        return new Promise((resolve, reject) => {
+            let user_ids = [];
+            let course_library_hash = {};
+            user.forEach(course => {
+                user_ids.push(course.id());
+                course_library_hash[course.id()] = course.owning_lib();
+            });
+            this.pcrud.search('acmcu', {user: user_ids}).subscribe(user => {
+                user.course(user_ids);
+                this.pcrud.autoApply(user).subscribe(res => {
+                    console.log(res);
+                }, err => {
+                    reject(err);
+                }, () => {
+                    resolve(user);
+                });
+            }, err => {
+                reject(err)
+            }, () => {
+                resolve(user_ids);
+            });
+        });
+    }
+
     resetItemFields(material, course_lib) {
         this.pcrud.retrieve('acp', material.item(),
             {flesh: 3, flesh_fields: {acp: ['call_number']}}).subscribe(copy => {