LP1888723 Angular volcopy UI mods and repairs
authorBill Erickson <berickxx@gmail.com>
Wed, 26 Aug 2020 22:09:11 +0000 (18:09 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Sun, 15 Aug 2021 23:55:12 +0000 (19:55 -0400)
* Use plus/minus buttons for adding and removing vols and copies.
* Support adding multiple vols and copies via add-multi pop-over
* Support hiding the Parts column
* Move Generate Barcodes and Use Checkdigit to bottom row.
* Collapse Batch Actions row by default, state stored in preferences.
* Fix issue where loading a record with no holdings would result in a
  mostly image page.
* Various display/layout repairs for showing/hiding columns

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Ruth Frasur <rfrasur@library.in.gov>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/cat/volcopy/config.component.html
Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.css
Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.html
Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.html
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.service.ts

index 128e369..ab8f41b 100644 (file)
             </li>
             <li class="list-group-item">
               <div class="form-check form-check-inline">
+                <input class="form-check-input" type="checkbox" id="hide-copy_part-column" 
+                  [(ngModel)]="volcopy.defaults.hidden.copy_part">
+                <label class="form-check-label" for="hide-copy_part-column" i18n>
+                  Hide Item Part
+                </label>
+              </div>
+            </li>
+            <li class="list-group-item">
+              <div class="form-check form-check-inline">
                 <input class="form-check-input" type="checkbox" 
                   id="volcopy-unified-interface" 
                   [(ngModel)]="volcopy.defaults.values.unified_display">
index 918b508..c846598 100644 (file)
@@ -1,43 +1,61 @@
-<eg-confirm-dialog 
-  #confirmDelVol
+<eg-confirm-dialog #confirmDelVol
   i18n-dialogTitle i18n-dialogBody
   dialogTitle="Delete Call Number?"
   dialogBody="Delete {{deleteVolCount}} Call Number(s) and All Associated Item(s)?">
 </eg-confirm-dialog>
 
-<eg-confirm-dialog 
-  #confirmDelCopy
+<eg-confirm-dialog #confirmDelCopy
   i18n-dialogTitle i18n-dialogBody
   dialogTitle="Delete Item?"
   dialogBody="Delete {{deleteCopyCount}} Item(s)?">
 </eg-confirm-dialog>
 
-<div class="row d-flex bg-faint mb-2 pb-1 pt-1 border border-dark rounded">
-  <div class="p-1" [ngStyle]="{flex: flexAt(1)}"> </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(2)}"> </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(3)}" *ngIf="displayColumn('classification')">
-    <div><label class="font-weight-bold" i18n>Classification</label></div>
-    <div>
-      <eg-combobox [smallFormControl]="true" [(ngModel)]="batchVolClass">
-        <eg-combobox-entry *ngFor="let cls of volcopy.commonData.acn_class"
-          [entryId]="cls.id()" [entryLabel]="cls.name()">
-        </eg-combobox-entry>
-      </eg-combobox>
-    </div>
+<div *ngIf="!volcopy.defaults.visible.batch_actions"
+  class="bg-faint mb-2 p-1 border border-dark rounded">
+  <button class="btn btn-sm btn-outline-dark label-with-material-icon" 
+    (click)="toggleBatchVisibility()">
+    <span i18n>Batch Actions</span>
+    <span class="material-icons">unfold_more</span>
+  </button>
+</div>
+
+<div *ngIf="volcopy.defaults.visible.batch_actions" 
+  class="row d-flex bg-faint mb-2 pb-1 pt-1 border border-dark rounded">
+  <div class="p-1" [ngStyle]="{flex: flexAt(1)}">
+    <div><label class="font-weight-bold" i18n>&nbsp;</label></div>
+    <button class="btn btn-sm btn-outline-dark label-with-material-icon" 
+      (click)="toggleBatchVisibility()">
+      <span i18n>Batch Actions</span>
+      <span class="material-icons">unfold_less</span>
+    </button>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(4)}" *ngIf="displayColumn('prefix')">
-    <div><label class="font-weight-bold" i18n>Prefix</label></div>
-    <div>
-      <eg-combobox [smallFormControl]="true" [(ngModel)]="batchVolPrefix">
-        <eg-combobox-entry *ngFor="let pfx of volcopy.commonData.acn_prefix"
-          [entryId]="pfx.id()" [entryLabel]="pfx.label()">
-        </eg-combobox-entry>
-      </eg-combobox>
-    </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(3)}">
+    <ng-container *ngIf="displayColumn('classification')">
+      <div><label class="font-weight-bold" i18n>Classification</label></div>
+      <div>
+        <eg-combobox [smallFormControl]="true" [(ngModel)]="batchVolClass">
+          <eg-combobox-entry *ngFor="let cls of volcopy.commonData.acn_class"
+            [entryId]="cls.id()" [entryLabel]="cls.name()">
+          </eg-combobox-entry>
+        </eg-combobox>
+      </div>
+    </ng-container>
+  </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(4)}">
+    <ng-container *ngIf="displayColumn('prefix')">
+      <div><label class="font-weight-bold" i18n>Prefix</label></div>
+      <div>
+        <eg-combobox [smallFormControl]="true" [(ngModel)]="batchVolPrefix">
+          <eg-combobox-entry *ngFor="let pfx of volcopy.commonData.acn_prefix"
+            [entryId]="pfx.id()" [entryLabel]="pfx.label()">
+          </eg-combobox-entry>
+        </eg-combobox>
+      </div>
+    </ng-container>
   </div>
   <div class="p-1" [ngStyle]="{flex: flexAt(5)}">
     <div>
-      <label class="font-weight-bold label-with-material-icon" i18n>
+      <label class="font-weight-bold" i18n>
         Call Number Label
       </label>
     </div>
       </eg-combobox>
     </div>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(6)}" *ngIf="displayColumn('suffix')">
-    <div><label class="font-weight-bold" i18n>Suffix</label></div>
-    <div>
-      <eg-combobox [smallFormControl]="true" [(ngModel)]="batchVolSuffix">
-        <eg-combobox-entry *ngFor="let sfx of volcopy.commonData.acn_suffix"
-          [entryId]="sfx.id()" [entryLabel]="sfx.label()">
-        </eg-combobox-entry>
-      </eg-combobox>
-    </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(6)}">
+    <ng-container *ngIf="displayColumn('suffix')">
+      <div><label class="font-weight-bold" i18n>Suffix</label></div>
+      <div>
+        <eg-combobox [smallFormControl]="true" [(ngModel)]="batchVolSuffix">
+          <eg-combobox-entry *ngFor="let sfx of volcopy.commonData.acn_suffix"
+            [entryId]="sfx.id()" [entryLabel]="sfx.label()">
+          </eg-combobox-entry>
+        </eg-combobox>
+      </div>
+    </ng-container>
   </div>
   <div class="p-1" [ngStyle]="{flex: flexAt(7)}">
     <div><label class="font-weight-bold" i18n>Batch</label></div>
       </button>
     </div>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(8)}">
-    <ng-container *ngIf="displayColumn('generate_barcodes')">
-      <div><label class="font-weight-bold" i18n>Generate Barcodes</label></div>
-      <button class="btn btn-sm btn-outline-dark label-with-material-icon"
-       (click)="generateBarcodes()">
-       <span i18n>Generate</span>
-       <span class="material-icons">arrow_downward</span>
-      </button>
-    </ng-container>
-  </div>
-  <div class="p-1" [ngStyle]="{flex: flexSpan(9, 10)}">
-    <ng-container *ngIf="displayColumn('generate_barcodes')">
-      <div><label class="font-weight-bold" i18n>Checkdigit</label></div>
-      <div class="form-check form-check-inline">
-        <input class="form-check-input" type="checkbox" 
-          (change)="saveUseCheckdigit()"
-          id="use-checkdigit" [(ngModel)]="useCheckdigit"/>
-        <label class="form-check-label" for="use-checkdigit" i18n>
-          Use Checkdigit
-        </label>
-      </div>
-    </ng-container>
-  </div>
+  <!-- needed for consistent column widths -->
+  <div class="p-1" [ngStyle]="{flex: flexAt(8)}"></div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(9)}"></div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(10)}"></div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(11)}"></div>
 </div>
 
-
-
 <div class="row d-flex mt-2 mb-2">
   <div class="p-1" [ngStyle]="{flex: flexAt(1)}">
     <span class="font-weight-bold" i18n>Owning Library
       </ng-container>
     </span>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(2)}">
-    <label class="font-weight-bold" i18n>Call Numbers</label>
-  </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(3)}" *ngIf="displayColumn('classification')">
-    <span class="font-weight-bold" i18n>Classification
-      <ng-container *ngIf="expand !== 3">
-        <button title="Expand Column" i18n-title 
-          class="material-icon-button" (click)="expand = 3">
-          &#x2197;
-        </button>
-      </ng-container>
-      <ng-container *ngIf="expand === 3">
-        <button title="Shrink Column" i18n-title 
-          class="material-icon-button" (click)="expand = null">
-          &#x2199;
-        </button>
-      </ng-container>
-    </span>
+  <div class="p-1" [ngStyle]="{flex: flexAt(3)}">
+    <ng-container *ngIf="displayColumn('classification')">
+      <span class="font-weight-bold" i18n>Classification
+        <ng-container *ngIf="expand !== 3">
+          <button title="Expand Column" i18n-title 
+            class="material-icon-button" (click)="expand = 3">
+            &#x2197;
+          </button>
+        </ng-container>
+        <ng-container *ngIf="expand === 3">
+          <button title="Shrink Column" i18n-title 
+            class="material-icon-button" (click)="expand = null">
+            &#x2199;
+          </button>
+        </ng-container>
+      </span>
+    </ng-container>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(4)}" *ngIf="displayColumn('prefix')">
-    <span class="font-weight-bold" i18n>Prefix
-      <ng-container *ngIf="expand !== 4">
-        <button title="Expand Column" i18n-title 
-          class="material-icon-button" (click)="expand = 4">
-          &#x2197;
-        </button>
-      </ng-container>
-      <ng-container *ngIf="expand === 4">
-        <button title="Shrink Column" i18n-title 
-          class="material-icon-button" (click)="expand = null">
-          &#x2199;
-        </button>
-      </ng-container>
-    </span>
+  <div class="p-1" [ngStyle]="{flex: flexAt(4)}">
+    <ng-container *ngIf="displayColumn('prefix')">
+      <span class="font-weight-bold" i18n>Prefix
+        <ng-container *ngIf="expand !== 4">
+          <button title="Expand Column" i18n-title 
+            class="material-icon-button" (click)="expand = 4">
+            &#x2197;
+          </button>
+        </ng-container>
+        <ng-container *ngIf="expand === 4">
+          <button title="Shrink Column" i18n-title 
+            class="material-icon-button" (click)="expand = null">
+            &#x2199;
+          </button>
+        </ng-container>
+      </span>
+    </ng-container>
   </div>
   <div class="p-1" [ngStyle]="{flex: flexAt(5)}">
     <span class="font-weight-bold" i18n>Call Number Label
       </ng-container>
     </span>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(6)}" *ngIf="displayColumn('suffix')">
-    <span class="font-weight-bold" i18n>Suffix
-      <ng-container *ngIf="expand !== 6">
-        <button title="Expand Column" i18n-title 
-          class="material-icon-button" (click)="expand = 6">
-          &#x2197;
-        </button>
-      </ng-container>
-      <ng-container *ngIf="expand === 6">
-        <button title="Shrink Column" i18n-title
-          class="material-icon-button" (click)="expand = null">
-          &#x2199;
-        </button>
-      </ng-container>
-    </span>
+  <div class="p-1" [ngStyle]="{flex: flexAt(6)}">
+    <ng-container *ngIf="displayColumn('suffix')">
+      <span class="font-weight-bold" i18n>Suffix
+        <ng-container *ngIf="expand !== 6">
+          <button title="Expand Column" i18n-title 
+            class="material-icon-button" (click)="expand = 6">
+            &#x2197;
+          </button>
+        </ng-container>
+        <ng-container *ngIf="expand === 6">
+          <button title="Shrink Column" i18n-title
+            class="material-icon-button" (click)="expand = null">
+            &#x2199;
+          </button>
+        </ng-container>
+      </span>
+    </ng-container>
   </div>
   <div class="p-1" [ngStyle]="{flex: flexAt(7)}">
-    <label class="font-weight-bold" i18n>Items</label>
+    <label class="font-weight-bold" i18n></label>
   </div>
   <!-- 
     When hiding the copy_number column, absorb its colum width to 
     take advantage of the space and to ensure the main columns still 
     line up with the batch updater row sitting above
   -->
-  <div class="p-1" 
-    [ngStyle]="{flex: displayColumn('copy_number_vc') ? flexAt(8) : flexSpan(8, 9)}">
+  <div class="p-1" [ngStyle]="{flex: flexAt(8)}">
     <span class="font-weight-bold" i18n>Barcode
       <ng-container *ngIf="expand !== 8">
         <button title="Expand Column" i18n-title 
       </ng-container>
     </span>
   </div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(9)}" *ngIf="displayColumn('copy_number_vc')">
-    <label class="font-weight-bold" i18n>Item #</label>
+  <div class="p-1" [ngStyle]="{flex: flexAt(9)}">
+    <ng-container *ngIf="displayColumn('copy_number_vc')">
+      <label class="font-weight-bold" i18n>Item #</label>
+    </ng-container>
   </div>
   <div class="p-1" [ngStyle]="{flex: flexAt(10)}">
-    <span class="font-weight-bold" i18n>Part
-      <ng-container *ngIf="expand !== 10">
-        <button title="Expand Column" i18n-title 
-          class="material-icon-button" (click)="expand = 10">
-          &#x2197;
-        </button>
-      </ng-container>
-      <ng-container *ngIf="expand === 10">
-        <button title="Shrink Column" i18n-title 
-          class="material-icon-button" (click)="expand = null">
-          &#x2199;
-        </button>
-      </ng-container>
-    </span>
+    <ng-container *ngIf="displayColumn('copy_part')">
+      <span class="font-weight-bold" i18n>Part
+        <ng-container *ngIf="expand !== 10">
+          <button title="Expand Column" i18n-title 
+            class="material-icon-button" (click)="expand = 10">
+            &#x2197;
+          </button>
+        </ng-container>
+        <ng-container *ngIf="expand === 10">
+          <button title="Shrink Column" i18n-title 
+            class="material-icon-button" (click)="expand = null">
+            &#x2199;
+          </button>
+        </ng-container>
+      </span>
+    </ng-container>
   </div>
+  <div class="p-1" [ngStyle]="{flex: flexAt(11)}"></div>
 </div>
 
 <ng-container *ngFor="let orgNode of context.orgNodes(); let orgIdx = index">
     <ng-container *ngFor="let copyNode of volNode.children; let copyIdx = index">
       <div class="row d-flex mt-1" [ngClass]="{'vol-row': copyIdx == 0}">
         <div class="p-1" [ngStyle]="{flex: flexAt(1)}">
-          <ng-container *ngIf="copyIdx == 0">
-            <span>{{orgNode.target.shortname()}}</span>
-            {{sessionType}}
-            <ng-container *ngIf="context.sessionType == 'record' || context.sessionType == 'mixed'">
-              <button class="clear-button" (click)="deleteVol(volNode)"
-                title="Delete Call Number {{volNode.target.label()}}" i18n-title>
-                <span class="material-icons">clear</span>
-              </button>
-            </ng-container>
-          </ng-container>
-        </div>
-        <div class="p-1" [ngStyle]="{flex: flexAt(2)}">
-          <ng-container *ngIf="copyIdx == 0 && volIdx == 0">
-            <input type="number" class="form-control form-control-sm"
-              [disabled]="context.sessionType == 'copy' || context.sessionType == 'vol'"
-              [required]="true" [min]="existingVolCount(orgNode)"
-              [ngModel]="orgNode.children.length"
-              (ngModelChange)="volCountChanged(orgNode, $event)"/>
-          </ng-container>
+          <div class="d-flex">
+            <div class="flex-1 pl-1">
+              <ng-container *ngIf="copyIdx == 0 && volIdx == 0">
+                <span>{{orgNode.target.shortname()}}</span>
+              </ng-container>
+            </div>
+            <div class="pr-1">
+              <ng-container *ngIf="copyIdx == 0 && volIdx == 0 && (
+                context.sessionType == 'record' || context.sessionType == 'mixed')">
+                  <ng-template #addOrgTmpl>
+                    <eg-org-select [limitPerms]="['CREATE_VOLUME']" 
+                      placeholder="Select Location..." i18n-placeholder
+                      [hideOrgs]="volcopy.hideVolOrgs"
+                      (onChange)="addVol($event); addOrgPopover.close()">
+                    </eg-org-select>
+                  </ng-template>
+
+                  <button class="btn btn-sm material-icon-button p-1"
+                    placement="bottom" [ngbPopover]="addOrgTmpl"
+                    autoClose="outside" #addOrgPopover="ngbPopover"
+                    i18n-popoverTitle="Add Call Number For Location"
+                    i18n-title title="Add Call Number For Location"
+                    (click)="addVolOrg=null">
+                    <span class="material-icons">add_circle_outline</span>
+                  </button>
+              </ng-container>
+            </div>
+          </div>
         </div>
-        <div class="p-1" [ngStyle]="{flex: flexAt(3)}" *ngIf="displayColumn('classification')">
-          <ng-container *ngIf="copyIdx == 0">
-            <eg-combobox
-              [selectedId]="volNode.target.label_class()"
-              [smallFormControl]="true"
-              [required]="true"
-              (onChange)="applyVolValue(volNode.target, 'label_class', $event ? $event.id : null)">
-              <eg-combobox-entry *ngFor="let cls of volcopy.commonData.acn_class"
-                [entryId]="cls.id()" [entryLabel]="cls.name()">
-              </eg-combobox-entry>
-            </eg-combobox>
+        <div class="p-1" [ngStyle]="{flex: flexAt(3)}">
+          <ng-container *ngIf="displayColumn('classification')">
+            <ng-container *ngIf="copyIdx == 0">
+              <eg-combobox
+                [selectedId]="volNode.target.label_class()"
+                [smallFormControl]="true"
+                [required]="true"
+                (onChange)="applyVolValue(volNode.target, 'label_class', $event ? $event.id : null)">
+                <eg-combobox-entry *ngFor="let cls of volcopy.commonData.acn_class"
+                  [entryId]="cls.id()" [entryLabel]="cls.name()">
+                </eg-combobox-entry>
+              </eg-combobox>
+            </ng-container>
           </ng-container>
         </div>
-        <div class="p-1" [ngStyle]="{flex: flexAt(4)}" *ngIf="displayColumn('prefix')">
-          <ng-container *ngIf="copyIdx == 0">
-            <eg-combobox
-              [selectedId]="volNode.target.prefix()"
-              [required]="true"
-              [smallFormControl]="true"
-              (onChange)="applyVolValue(volNode.target, 'prefix', $event ? $event.id : null)">
-              <eg-combobox-entry
-                [entryId]="-1" entryLabel="<None>" i18n-entryLabel>
-              </eg-combobox-entry>
-              <eg-combobox-entry *ngFor="let pfx of volcopy.commonData.acn_prefix"
-                [entryId]="pfx.id()" [entryLabel]="pfx.label()">
-              </eg-combobox-entry>
-            </eg-combobox>
+        <div class="p-1" [ngStyle]="{flex: flexAt(4)}">
+          <ng-container *ngIf="displayColumn('prefix')">
+            <ng-container *ngIf="copyIdx == 0">
+              <eg-combobox
+                [selectedId]="volNode.target.prefix()"
+                [required]="true"
+                [smallFormControl]="true"
+                (onChange)="applyVolValue(volNode.target, 'prefix', $event ? $event.id : null)">
+                <eg-combobox-entry
+                  [entryId]="-1" entryLabel="<None>" i18n-entryLabel>
+                </eg-combobox-entry>
+                <eg-combobox-entry *ngFor="let pfx of volcopy.commonData.acn_prefix"
+                  [entryId]="pfx.id()" [entryLabel]="pfx.label()">
+                </eg-combobox-entry>
+              </eg-combobox>
+            </ng-container>
           </ng-container>
         </div>
         <div class="p-1" [ngStyle]="{flex: flexAt(5)}">
               (change)="applyVolValue(volNode.target, 'label', $event.target.value)">
           </ng-container>
         </div>
-        <div class="p-1" [ngStyle]="{flex: flexAt(6)}" *ngIf="displayColumn('suffix')">
-          <ng-container *ngIf="copyIdx == 0">
-            <eg-combobox
-              [selectedId]="volNode.target.suffix()"
-              [required]="true"
-              [smallFormControl]="true"
-              (onChange)="applyVolValue(volNode.target, 'suffix', $event ? $event.id : null)">
-              <eg-combobox-entry
-                [entryId]="-1" entryLabel="<None>" i18n-entryLabel>
-              </eg-combobox-entry>
-              <eg-combobox-entry *ngFor="let sfx of volcopy.commonData.acn_suffix"
-                [entryId]="sfx.id()" [entryLabel]="sfx.label()">
-              </eg-combobox-entry>
-            </eg-combobox>
+        <div class="p-1" [ngStyle]="{flex: flexAt(6)}">
+          <ng-container *ngIf="displayColumn('suffix')">
+            <ng-container *ngIf="copyIdx == 0">
+              <eg-combobox
+                [selectedId]="volNode.target.suffix()"
+                [required]="true"
+                [smallFormControl]="true"
+                (onChange)="applyVolValue(volNode.target, 'suffix', $event ? $event.id : null)">
+                <eg-combobox-entry
+                  [entryId]="-1" entryLabel="<None>" i18n-entryLabel>
+                </eg-combobox-entry>
+                <eg-combobox-entry *ngFor="let sfx of volcopy.commonData.acn_suffix"
+                  [entryId]="sfx.id()" [entryLabel]="sfx.label()">
+                </eg-combobox-entry>
+              </eg-combobox>
+            </ng-container>
           </ng-container>
         </div>
         <div class="p-1" [ngStyle]="{flex: flexAt(7)}">
           <ng-container *ngIf="copyIdx == 0">
-            <input type="number" class="form-control form-control-sm"
-              [disabled]="context.sessionType == 'copy'"
-              [required]="true" [min]="existingCopyCount(volNode)"
-              [ngModel]="volNode.children.length"
-              (ngModelChange)="copyCountChanged(volNode, $event)"/>
+            <ng-container 
+              *ngIf="context.sessionType == 'record' || context.sessionType == 'mixed'">
+              <button class="btn btn-sm material-icon-button p-1"
+                (click)="createVols(orgNode, 1)"
+                i18n-title title="Add Call Number">
+                <span class="material-icons">add_circle_outline</span>
+              </button>
+
+              <ng-template #addVolCountTmpl>
+                <div i18n>Add How Many Call Numbers</div>
+                <div class="form-inline mt-1">
+                  <input type="number" class="form-control form-control-sm"
+                    id='add-vol-popover' 
+                    (keyup.enter)="createVolsFromPopover(orgNode, addVolsPopover)"
+                    [(ngModel)]="addVolCount" [required]="true" min="1"/>
+                  <button class="btn btn-sm btn-success ml-1" 
+                    (click)="createVolsFromPopover(orgNode, addVolsPopover)"
+                    i18n>Add</button>
+                </div>
+              </ng-template>
+
+              <button class="btn btn-sm material-icon-button p-1"
+                [disabled]="context.sessionType == 'copy' || context.sessionType == 'vol'"
+                (shown)="focusElement('add-vol-popover')"
+                placement="bottom" [ngbPopover]="addVolCountTmpl"
+                autoClose="outside" #addVolsPopover="ngbPopover"
+                i18n-popoverTitle="Add Call Numbers"
+                i18n-title title="Add Call Numbers">
+                <span class="material-icons">playlist_add</span>
+              </button>
+
+              <button class="btn btn-sm material-icon-button p-1"
+                (click)="deleteVol(volNode)"
+                i18n-title title="Remove Call Number">
+                <span class="material-icons">remove_circle_outline</span>
+              </button>
+
+            </ng-container>
           </ng-container>
         </div>
-        <div class="p-1" 
-          [ngStyle]="{flex: displayColumn('copy_number_vc') ? flexAt(8) : flexSpan(8, 9)}">
+        <div class="p-1" [ngStyle]="{flex: flexAt(8)}">
           <div class="d-flex">
-            <ng-container *ngIf="context.sessionType != 'copy'">
-              <button class="clear-button" (click)="deleteCopy(copyNode)"
-                [disabled]="volcopy.restrictCopyDelete(copyNode.target.status())"
-                title="Delete Item {{copyNode.target.barcode()}}" i18n-title>
-                <span class="material-icons">clear</span>
-              </button>
-            </ng-container>
-
             <!--
               Barcode value is not required for new copies, since those 
               without a barcode will be ignored.
             Duplicate Barcode
           </div>
         </div>
-        <div class="p-1" [ngStyle]="{flex: flexAt(9)}" *ngIf="displayColumn('copy_number_vc')">
-          <input type="number" min="1" class="form-control form-control-sm"
-            [ngModel]="copyNode.target.copy_number()"
-            (ngModelChange)="applyCopyValue(copyNode.target, 'copy_number', $event)"/>
+        <div class="p-1" [ngStyle]="{flex: flexAt(9)}">
+          <ng-container *ngIf="displayColumn('copy_number_vc')">
+            <input type="number" min="1" class="form-control form-control-sm"
+              [ngModel]="copyNode.target.copy_number()"
+              (ngModelChange)="applyCopyValue(copyNode.target, 'copy_number', $event)"/>
+          </ng-container>
         </div>
         <div class="p-1" [ngStyle]="{flex: flexAt(10)}">
-          <ng-container *ngIf="!recordHasParts(volNode.target.record())">
-            <label i18n>N/A</label>
+          <ng-container *ngIf="displayColumn('copy_part')">
+            <ng-container *ngIf="!recordHasParts(volNode.target.record())">
+              <label i18n>N/A</label>
+            </ng-container>
+            <ng-container *ngIf="recordHasParts(volNode.target.record())">
+              <eg-combobox
+                [selectedId]="copyNode.target.parts()[0] ? copyNode.target.parts()[0].id() : null"
+                [smallFormControl]="true"
+                (onChange)="copyPartChanged(copyNode, $event)">
+                <eg-combobox-entry 
+                  *ngFor="let part of volcopy.bibParts[volNode.target.record()]"
+                  [entryId]="part.id()" [entryLabel]="part.label()">
+                </eg-combobox-entry>
+              </eg-combobox>
+            </ng-container>
           </ng-container>
-          <ng-container *ngIf="recordHasParts(volNode.target.record())">
-            <eg-combobox
-              [selectedId]="copyNode.target.parts()[0] ? copyNode.target.parts()[0].id() : null"
-              [smallFormControl]="true"
-              (onChange)="copyPartChanged(copyNode, $event)">
-              <eg-combobox-entry 
-                *ngFor="let part of volcopy.bibParts[volNode.target.record()]"
-                [entryId]="part.id()" [entryLabel]="part.label()">
-              </eg-combobox-entry>
-            </eg-combobox>
+        </div>
+        <div class="p-1" [ngStyle]="{flex: flexAt(11)}">
+          <ng-container *ngIf="context.sessionType != 'copy'">
+
+            <button class="btn btn-sm material-icon-button p-1"
+              (click)="createCopies(volNode, 1)" i18n-title title="Add Item">
+              <span class="material-icons">add_circle_outline</span>
+            </button>
+
+            <ng-template #addCopyCountTmpl>
+              <div i18n>Add How Many Items</div>
+              <div class="form-inline mt-1">
+                <input type="number" class="form-control form-control-sm"
+                  id="add-copy-popover"
+                  (keyup.enter)="createCopiesFromPopover(volNode, addCopiesPopover)"
+                  [(ngModel)]="addCopyCount" [required]="true" min="1"/>
+                <button class="btn btn-sm btn-success ml-1" 
+                  (click)="createCopiesFromPopover(volNode, addCopiesPopover)"
+                  i18n>Add</button>
+              </div>
+            </ng-template>
+
+            <button class="btn btn-sm material-icon-button p-1"
+              placement="left" [ngbPopover]="addCopyCountTmpl"
+              autoClose="outside" #addCopiesPopover="ngbPopover"
+              i18n-popoverTitle="Add Items" i18n-title title="Add Items"
+              (shown)="focusElement('add-copy-popover')">
+              <span class="material-icons">playlist_add</span>
+            </button>
+
+            <button class="btn btn-sm material-icon-button p-1"
+              (click)="deleteCopy(copyNode)" i18n-title title="Remove Item">
+              <span class="material-icons">remove_circle_outline</span>
+            </button>
+
           </ng-container>
         </div>
       </div>
 
 <hr/>
 
-<div class="row d-flex">
-
-  <div class="p-1" [ngStyle]="{flex: flexSpan(1, 2)}">
-    <eg-org-select #newVolOrg [applyDefault]="true" 
-      [limitPerms]="['CREATE_VOLUME']" [hideOrgs]="volcopy.hideVolOrgs">
-    </eg-org-select>
-  </div>
-
-  <div class="p-1" [ngStyle]="{flex: flexSpan(3, 4)}">
-    <button class="btn btn-outline-dark ml-2" 
-      (click)="addVol(newVolOrg.selectedOrg())" i18n>
-      Add Call Number
-    </button>
-  </div>
-
-  <div class="p-1" [ngStyle]="{flex: flexAt(5)}"></div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(6)}"></div>
-  <div class="p-1" [ngStyle]="{flex: flexAt(7)}"></div>
-
-  <div class="p-1 pl-3" [ngStyle]="{flex: flexAt(8)}">
-    <ng-container *ngIf="displayColumn('generate_barcodes')">
-      <button class="btn btn-sm btn-outline-dark mr-2"
-        (click)="generateBarcodes()" i18n>Generate Barcodes</button>
-    </ng-container>
-  </div>
-
-  <div class="p-1" [ngStyle]="{flex: flexSpan(9, 10)}">
-    <ng-container *ngIf="displayColumn('generate_barcodes')">
-      <div class="form-check form-check-inline mr-2">
-        <input class="form-check-input" type="checkbox" 
-          (change)="saveUseCheckdigit()"
-          id="use-checkdigit-2" [(ngModel)]="useCheckdigit"/>
-        <label class="form-check-label" for="use-checkdigit-2" i18n>
-          Use Checkdigit
-        </label>
-      </div>
-    </ng-container>
-  </div>
-</div>
-  
index 0d0b4ce..f12dae1 100644 (file)
@@ -28,7 +28,17 @@ export class VolEditComponent implements OnInit {
     // markup.  Changing a flex value here will propagate to all
     // rows in the form.  Column numbers are 1-based.
     flexSettings: {[column: number]: number} = {
-        1: 1, 2: 1, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 1, 10: 1};
+        1: 2, 2: 1, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 1, 10: 1, 11: 1};
+
+    // Since visibility of some columns is configurable, we need a
+    // map of configured column name to column index.
+    flexColMap = {
+        3: 'classification',
+        4: 'prefix',
+        6: 'suffix',
+        9: 'copy_number_vc',
+        10: 'copy_part'
+    };
 
     // If a column is specified as the expand field, its flex value
     // will magically grow.
@@ -40,11 +50,16 @@ export class VolEditComponent implements OnInit {
     batchVolLabel: ComboboxEntry;
 
     autoBarcodeInProgress = false;
-    useCheckdigit = false;
 
     deleteVolCount: number = null;
     deleteCopyCount: number = null;
 
+    // When adding multiple vols via add-many popover.
+    addVolCount: number = null;
+
+    // When adding multiple copies via add-many popover.
+    addCopyCount: number = null;
+
     recordVolLabels: string[] = [];
 
     @ViewChild('confirmDelVol', {static: false})
@@ -71,7 +86,8 @@ export class VolEditComponent implements OnInit {
 
         this.deleteVolCount = null;
         this.deleteCopyCount = null;
-        this.useCheckdigit = this.volcopy.defaults.values.use_checkdigit;
+
+        this.volcopy.genBarcodesRequested.subscribe(() => this.generateBarcodes());
 
         this.volcopy.fetchRecordVolLabels(this.context.recordId)
         .then(labels => this.recordVolLabels = labels)
@@ -96,55 +112,22 @@ export class VolEditComponent implements OnInit {
 
     // Column width (flex:x) for column by column number.
     flexAt(column: number): number {
-        return this.flexSpan(column, column);
-    }
-
-    // Returns the flex amount occupied by a span of columns.
-    flexSpan(column1: number, column2: number): number {
-        let flex = 0;
-        for (let i = column1; i <= column2; i++) {
-            let value = this.flexSettings[i];
-            if (this.expand === i) { value = value * 3; }
-            flex += value;
-        }
-        return flex;
-    }
-
-    volCountChanged(orgNode: HoldingsTreeNode, count: number) {
-        if (count === null) { return; }
-        const diff = count - orgNode.children.length;
-        if (diff > 0) {
-            this.createVols(orgNode, diff);
-        } else if (diff < 0) {
-            this.deleteVols(orgNode, -diff);
+        if (!this.displayColumn(this.flexColMap[column])) {
+            // Hidden columsn are still present, but they do not
+            // flex and the contain no data to display
+            return 0;
         }
+        let value = this.flexSettings[column];
+        if (this.expand === column) { value = value * 3; }
+        return value;
     }
 
-
     addVol(org: IdlObject) {
         if (!org) { return; }
         const orgNode = this.context.findOrCreateOrgNode(org.id());
         this.createVols(orgNode, 1);
     }
 
-    existingVolCount(orgNode: HoldingsTreeNode): number {
-        return orgNode.children.filter(volNode => !volNode.target.isnew()).length;
-    }
-
-    existingCopyCount(volNode: HoldingsTreeNode): number {
-        return volNode.children.filter(copyNode => !copyNode.target.isnew()).length;
-    }
-
-    copyCountChanged(volNode: HoldingsTreeNode, count: number) {
-        if (count === null) { return; }
-        const diff = count - volNode.children.length;
-        if (diff > 0) {
-            this.createCopies(volNode, diff);
-        } else if (diff < 0) {
-            this.deleteCopies(volNode, -diff);
-        }
-    }
-
     // This only removes copies that were created during the
     // current editing session and have not yet been saved in the DB.
     deleteCopies(volNode: HoldingsTreeNode, count: number) {
@@ -169,6 +152,17 @@ export class VolEditComponent implements OnInit {
         }
     }
 
+    createCopiesFromPopover(volNode: HoldingsTreeNode, popover: any) {
+        this.createCopies(volNode, this.addCopyCount);
+        popover.close();
+        this.addCopyCount = null;
+    }
+
+    createVolsFromPopover(orgNode: HoldingsTreeNode, popover: any) {
+        this.createVols(orgNode, this.addVolCount);
+        popover.close();
+        this.addVolCount = null;
+    }
 
     createVols(orgNode: HoldingsTreeNode, count: number) {
         const vols = [];
@@ -344,7 +338,7 @@ export class VolEditComponent implements OnInit {
         return this.net.request('open-ils.cat',
             'open-ils.cat.item.barcode.autogen',
             this.auth.token(), seedBarcode, count, {
-                checkdigit: this.useCheckdigit,
+                checkdigit: this.volcopy.defaults.values.use_checkdigit,
                 skip_dupes: true
             }
         ).pipe(tap(barcodes => {
@@ -503,11 +497,6 @@ export class VolEditComponent implements OnInit {
         return this.volcopy.defaults.hidden[field] !== true;
     }
 
-    saveUseCheckdigit() {
-        this.volcopy.defaults.values.use_checkdigit = this.useCheckdigit === true;
-        this.volcopy.saveDefaults();
-    }
-
     canSave(): boolean {
 
         const copies = this.context.copyList();
@@ -535,5 +524,20 @@ export class VolEditComponent implements OnInit {
             this.canSaveChange.emit(this.canSave());
         });
     }
+
+    // Given a DOM ID, focus the element after a 0 timeout.
+    focusElement(domId: string) {
+        setTimeout(() => {
+            const node = document.getElementById(domId);
+            if (node) { node.focus(); }
+        });
+    }
+
+
+    toggleBatchVisibility() {
+        this.volcopy.defaults.visible.batch_actions =
+            !this.volcopy.defaults.visible.batch_actions;
+        this.volcopy.saveDefaults();
+    }
 }
 
index be7ff28..ccb5e5b 100644 (file)
     <hr class="m-2"/>                                                          
     <div class="row m-2 p-2 border border-dark rounded bg-faint">
       <div class="col-lg-12 d-flex">
+
         <div class="form-check form-check-inline ml-2">                            
           <input class="form-check-input" id='use-labels-cbox' type="checkbox"     
-            [(ngModel)]="printLabels" (change)="savePrintLabels()">
+            [ngModel]="volcopy.defaults.values.print_labels"
+            (change)="toggleCheckbox('print_labels')">
           <label class="form-check-label" for='use-labels-cbox'                    
             i18n>Print Labels?</label>                                             
         </div>
+
+        <ng-container *ngIf="tab === 'holdings' && 
+          volcopy.defaults.hidden.generate_barcodes !== true">
+          <!-- 
+            These actions could cause confusion or unintended
+            consequences if visible on any other tabs
+          -->
+          <div class="form-check form-check-inline">
+            <input class="form-check-input" type="checkbox" id="use-checkdigit" 
+              (change)="toggleCheckbox('use_checkdigit')"
+              [ngModel]="volcopy.defaults.values.use_checkdigit"/>
+            <label class="form-check-label" for="use-checkdigit" i18n>
+              Use Checkdigit
+            </label>
+          </div>
+          <button class="btn btn-sm btn-outline-dark label-with-material-icon"
+            (click)="volcopy.genBarcodesRequested.emit()">
+            <span i18n>Generate Barcodes</span>
+            <span class="material-icons">refresh</span>
+          </button>
+        </ng-container>
+
         <div class="flex-1"> </div>
         <button class="btn btn-outline-dark" (click)="save()" 
           [ngClass]="{'border-danger': isNotSaveable()}"
index 08eabd6..1864f76 100644 (file)
@@ -53,7 +53,6 @@ export class VolCopyComponent implements OnInit {
     context: VolCopyContext;
     loading = true;
     sessionExpired = false;
-    printLabels = false;
 
     tab = 'holdings'; // holdings | attrs | config
     target: string;   // item | callnumber | record | session
@@ -132,8 +131,6 @@ export class VolCopyComponent implements OnInit {
             this.context.volNodes().map(n => n.target)))
         .then(_ => this.context.sortHoldings())
         .then(_ => this.context.setRecordId())
-        .then(_ => this.printLabels =
-            this.volcopy.defaults.values.print_labels === true)
         .then(_ => {
             // unified display has no 'attrs' tab
             if (this.volcopy.defaults.values.unified_display
@@ -285,6 +282,7 @@ export class VolCopyComponent implements OnInit {
 
     fetchCopies(copyIds: number | number[]): Promise<any> {
         const ids = [].concat(copyIds);
+        if (ids.length === 0) { return Promise.resolve(); }
         return this.pcrud.search('acp', {id: ids}, COPY_FLESH)
         .pipe(tap(copy => this.context.findOrCreateCopyNode(copy)))
         .toPromise();
@@ -293,6 +291,7 @@ export class VolCopyComponent implements OnInit {
     // Fetch call numbers and linked copies by call number ids.
     fetchVols(volIds?: number | number[]): Promise<any> {
         const ids = [].concat(volIds);
+        if (ids.length === 0) { return Promise.resolve(); }
 
         return this.pcrud.search('acn', {id: ids})
         .pipe(tap(vol => this.context.findOrCreateVolNode(vol)))
@@ -466,13 +465,16 @@ export class VolCopyComponent implements OnInit {
         });
     }
 
-    savePrintLabels() {
-        this.volcopy.defaults.values.print_labels = this.printLabels === true;
+    toggleCheckbox(field: string) {
+        this.volcopy.defaults.values[field] =
+            !this.volcopy.defaults.values[field];
         this.volcopy.saveDefaults();
     }
 
     openPrintLabels(copyIds?: number[]): Promise<any> {
-        if (!this.printLabels) { return Promise.resolve(); }
+        if (!this.volcopy.defaults.values.print_labels) {
+            return Promise.resolve();
+        }
 
         if (!copyIds || copyIds.length === 0) {
             copyIds = this.context.copyList()
index 891a46c..7d065ec 100644 (file)
@@ -1,4 +1,4 @@
-import {Injectable} from '@angular/core';
+import {Injectable, EventEmitter} from '@angular/core';
 import {Observable} from 'rxjs';
 import {map, tap, mergeMap} from 'rxjs/operators';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
@@ -16,8 +16,12 @@ import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.comp
 /* Managing volcopy data */
 
 interface VolCopyDefaults {
+    // Default values per field.
     values: {[field: string]: any};
+    // Most fields are visible by default.
     hidden: {[field: string]: boolean};
+    // ... But some fields are hidden by default.
+    visible: {[field: string]: boolean};
 }
 
 @Injectable()
@@ -47,6 +51,9 @@ export class VolCopyService {
 
     hideVolOrgs: number[] = [];
 
+    // Currently spans from volcopy.component to vol-edit.component.
+    genBarcodesRequested: EventEmitter<void> = new EventEmitter<void>();
+
     constructor(
         private evt: EventService,
         private net: NetService,
@@ -161,7 +168,10 @@ export class VolCopyService {
 
         return this.serverStore.getItem('eg.cat.volcopy.defaults').then(
             (defaults: VolCopyDefaults) => {
-                this.defaults = defaults || {values: {}, hidden: {}};
+                this.defaults = defaults || {values: {}, hidden: {}, visible: {}};
+                if (!this.defaults.values)  { this.defaults.values  = {}; }
+                if (!this.defaults.hidden)  { this.defaults.hidden  = {}; }
+                if (!this.defaults.visible) { this.defaults.visible = {}; }
             }
         );
     }