Angular holdings maintenance wip
authorBill Erickson <berickxx@gmail.com>
Fri, 15 Mar 2019 22:24:55 +0000 (18:24 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 18 Mar 2019 14:28:42 +0000 (10:28 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html
Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts

index 3f0d5c7..4ae2f66 100644 (file)
 
 <div class='eg-copies w-100 mt-3'>
   <eg-grid #holdingsGrid [dataSource]="gridDataSource"
+    (onRowActivate)="onRowActivate($event)"
     [pageSize]="50" [rowClassCallback]="rowClassCallback"
     [sortable]="false" persistKey="catalog.record.holdings">
+
+    <!-- checkboxes -->
+
+    <!-- TODO TODO XXXX The onChange handlers will migrate to (onChange) event
+          handler in another pending branch !-->
+    <eg-grid-toolbar-checkbox i18n-label label="Show Volumes"            
+      [onChange]="toggleShowVolumes"></eg-grid-toolbar-checkbox> 
+
+    <!-- fields -->
     <eg-grid-column path="index" [hidden]="true" [index]="true">
     </eg-grid-column>
     <eg-grid-column path="copy.id" [hidden]="true" label="Copy ID" i18n-label>
index 92da8f9..fa65a52 100644 (file)
@@ -19,6 +19,8 @@ class HoldingsTreeNode {
     target: any;
     parentNode: HoldingsTreeNode;
     expanded: boolean;
+    copyCount: number;
+    volumeCount: number;
     constructor() {
         this.children = [];
     }
@@ -65,8 +67,13 @@ export class HoldingsMaintenanceComponent implements OnInit {
     holdingsTree: HoldingsTree;
     holdingsTreeOrgCache: {[id: number]: HoldingsTreeNode};
     refreshHoldings: boolean;
+    gridIndex: number;
     rowClassCallback: (row: any) => string;
 
+    // TODO XXXX The checkbox toggles will change to @Output handlers
+    // in a pending catalog working branch.
+    toggleShowVolumes: (value: boolean) => void;
+
     @Input() set recordId(id: number) {
         this.recId = id;
         // Only force new data collection when recordId()
@@ -93,7 +100,7 @@ export class HoldingsMaintenanceComponent implements OnInit {
 
         this.rowClassCallback = (row: any): string => {
              if (row.volume && !row.copy) {
-                return 'border border-info';
+                return 'bg-info';
             }
         }
 
@@ -114,6 +121,23 @@ export class HoldingsMaintenanceComponent implements OnInit {
                 this.holdingsGrid.reload();
             }
         }
+
+
+        // TODO XXXX The checkbox toggles will change to @Output handlers
+        // in a pending catalog working branch.
+        this.toggleShowVolumes = (value: boolean) => {
+            console.log('SHOW VOLS', value);
+            this.showVolumes = value;
+            this.holdingsGrid.reload();
+        }
+    }
+
+    onRowActivate(row: any) {
+        if (row.copy) {
+            // Launch copy editor?
+        } else {
+            this.gridTemplateContext.toggleExpandRow(row);
+        }
     }
 
     ngOnInit() {
@@ -150,63 +174,112 @@ export class HoldingsMaintenanceComponent implements OnInit {
         traverseOrg(this.holdingsTree.root);
     }
 
-    flattenHoldingsTree(observer: Observer<HoldingsEntry>) {
-
-        let index = 0;
-
-        const traverseTree = (node: HoldingsTreeNode) => {
-
-            const entry = new HoldingsEntry();
-            entry.treeNode = node;
-            entry.index = index++;
-
-            switch(node.nodeType) {
-                case 'org':
-                    entry.locationLabel = node.target.shortname();
-                    entry.locationDepth = node.target.ou_type().depth();
-
-                    // Org node children are sorted with any child org nodes
-                    // pushed to the front, followed by the call number nodes
-                    // sorted alphabetcially by label.
-                    // TODO: prefix/suffix
-                    node.children = node.children.sort((a, b) => {
-                        if (a.nodeType === 'org') {
-                            if (b.nodeType === 'org') {
-                                return a.target.shortname() < b.target.shortname() ? -1 : 1;
-                            } else {
-                                return -1;
-                            }
-                        } else if (b.nodeType === 'org') {
-                            return 1;
-                        } else {
-                            return a.target.label() < b.target.label() ? -1 : 1;
-                        }
-                    });
-
-                    break;
-
-                case 'volume':
-                    entry.locationLabel = node.target.label(); // TODO prefix/suffix
-                    entry.locationDepth = node.parentNode.target.ou_type().depth() + 1;
-                    entry.callNumberLabel = entry.locationLabel;
-                    entry.volume = node.target;
-                    break;
-
-                case 'copy':
-                    entry.locationLabel = node.target.barcode();
-                    entry.locationDepth = node.parentNode.parentNode.target.ou_type().depth() + 2;
-                    entry.callNumberLabel = node.parentNode.target.label() // TODO
-                    entry.volume = node.parentNode.target;
-                    entry.copy = node.target;
+    // Org node children are sorted with any child org nodes pushed to the
+    // front, followed by the call number nodes sorted alphabetcially by label.
+    // TODO: prefix/suffix
+    sortOrgNodeChildren(node: HoldingsTreeNode) {
+        node.children = node.children.sort((a, b) => {
+            if (a.nodeType === 'org') {
+                if (b.nodeType === 'org') {
+                    return a.target.shortname() < b.target.shortname() ? -1 : 1;
+                } else {
+                    return -1;
+                }
+            } else if (b.nodeType === 'org') {
+                return 1;
+            } else {
+                return a.target.label() < b.target.label() ? -1 : 1;
             }
+        });
+    }
+
+    // Sets call number and copy count sums to nodes that need it.
+    setTreeCounts(node: HoldingsTreeNode) {
+
+        if (node.nodeType === 'org') {
+            node.copyCount = 0;
+            node.volumeCount = 0;
+        } else if(node.nodeType === 'volume') {
+            node.copyCount = 0;
+        }
 
-            observer.next(entry);
-            if (node.expanded) {
-                node.children.forEach(traverseTree);
+        node.children.forEach(child => {
+            this.setTreeCounts(child);
+            if (node.nodeType === 'org') {
+                node.copyCount += child.copyCount;
+                if (child.nodeType === 'volume') {
+                    node.volumeCount++;
+                } else {
+                    node.volumeCount += child.volumeCount;
+                }
+            } else if (node.nodeType === 'volume') {
+                node.copyCount = node.children.length;
             }
+        });
+    }
+
+    // Create HoldingsEntry objects for tree nodes that should be displayed
+    // and relays them to the grid via the observer.
+    propagateTreeEntries(observer: Observer<HoldingsEntry>, node: HoldingsTreeNode) {
+        const entry = new HoldingsEntry();
+        entry.treeNode = node;
+        entry.index = this.gridIndex++;
+
+        switch(node.nodeType) {
+            case 'org':
+                // Confirm the user wants to see this node
+                if (!this.showEmptyLibs) {
+                    if (node.volumeCount === 0) {
+                        return;
+                    }
+                }
+                entry.locationLabel = node.target.shortname();
+                entry.locationDepth = node.target.ou_type().depth();
+                entry.copyCount = node.copyCount;
+                entry.volumeCount = node.volumeCount;
+                this.sortOrgNodeChildren(node);
+                break;
+
+            case 'volume':
+                // Confirm the user wants to see this node
+                if (!this.showVolumes) {
+                    return;
+                }
+                if (!this.showEmptyVolumes && node.copyCount === 0) {
+                    return;
+                }
+
+                entry.locationLabel = node.target.label(); // TODO prefix/suffix
+                entry.locationDepth = node.parentNode.target.ou_type().depth() + 1;
+                entry.callNumberLabel = entry.locationLabel;
+                entry.volume = node.target;
+                entry.copyCount = node.copyCount;
+                break;
+
+            case 'copy':
+                entry.locationLabel = node.target.barcode();
+                entry.locationDepth = node.parentNode.parentNode.target.ou_type().depth() + 2;
+                entry.callNumberLabel = node.parentNode.target.label() // TODO
+                entry.volume = node.parentNode.target;
+                entry.copy = node.target;
+                break;
         }
 
-        traverseTree(this.holdingsTree.root);
+        // Tell the grid about the node entry
+        observer.next(entry);
+
+        if (node.expanded) {
+            // Process the child nodes.
+            node.children.forEach(child =>
+                this.propagateTreeEntries(observer, child));
+        }
+    }
+
+    // Turns the tree into a list of entries for grid display
+    flattenHoldingsTree(observer: Observer<HoldingsEntry>) {
+        this.gridIndex = 0;
+        this.setTreeCounts(this.holdingsTree.root);
+        this.propagateTreeEntries(observer, this.holdingsTree.root);
         observer.complete();
     }