Bunch of UI improvements: date restriction, progress dialogs, next-focus, success...
authorMike Rylander <mrylander@gmail.com>
Fri, 9 Jun 2017 21:54:20 +0000 (17:54 -0400)
committerMike Rylander <mrylander@gmail.com>
Fri, 9 Jun 2017 21:55:10 +0000 (17:55 -0400)
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Open-ILS/src/templates/staff/base_js.tt2
Open-ILS/src/templates/staff/offline-interface.tt2
Open-ILS/src/templates/staff/share/t_datetime.tt2
Open-ILS/web/js/ui/default/staff/offline.js
Open-ILS/web/js/ui/default/staff/services/ui.js

index edc16e0..73a5429 100644 (file)
@@ -62,6 +62,7 @@ UpUp.start({
     '[% ctx.media_prefix %]/js/ui/default/staff/offline.js',
     '[% ctx.base_path %]/staff/share/t_alert_dialog',
     '[% ctx.base_path %]/staff/share/t_datetime',
+    '[% ctx.base_path %]/staff/share/t_progress_dialog',
     '[% ctx.base_path %]/staff/share/print_templates/t_offline_in_house_use',
     '[% ctx.base_path %]/staff/share/print_templates/t_offline_checkout',
     '[% ctx.base_path %]/staff/share/print_templates/t_offline_checkin',
@@ -171,7 +172,7 @@ UpUp.start({
     s.BAD_PATRON_BARCODE = "[% l('Bad patron barcode') %]";
     s.ITEM_NOT_FOUND = "[% l('Item not found') %]";
     s.CONFIRM_CLEAR_PENDING = "[% l('Clear pending transactions') %]";
-    s.CONFIRM_CLEAR_PENDING_BODY = "[% l('Are you certain you want to clear these pending offline transactions?  This action is not reversable, and they cannot be recovered after clearing!') %]";
+    s.CONFIRM_CLEAR_PENDING_BODY = "[% l('Are you certain you want to clear these pending offline transactions? This action is irreversible. Transactions cannot be recovered after clearing!') %]";
   }]);
 </script>
 
index d75a074..49451e5 100644 (file)
         </button>
         <button
           class="btn btn-default"
+          ng-disabled="!printed"
+          ng-click="reprintLast()">
+            [% l('Reprint Last Receipt') %]
+        </button>
+        <button
+          class="btn btn-default"
           ng-if="logged_in"
           ng-click="downloadBlockList()">
             [% l('Download block list') %]
         </button>
+        <button
+          class="btn btn-default"
+          ng-disabled="pending_xacts.length == 0"
+          eg-line-exporter
+          ng-if="!logged_in"
+          default-file-name="pending.xacts"
+          json-array="pending_xacts"
+        >[% l('Export Transactions') %]</button>
       </div>
     </div>
   </div>
@@ -55,7 +69,7 @@
                 [% l('Due Date:') %]
               </div>
               <div class="col-md-4">
-                <eg-date-input id="co_duedate" ng-model="shared.due_date"></eg-date-input>
+                <eg-date-input id="co_duedate" ng-model="shared.due_date" min-date="minDate"></eg-date-input>
               </div>
               <div class="col-md-3">
                 <select class="form-control" ng-model="shared.due_date_offset" ng-change="resetDueDate()">
 
             <div class="row pad-vert">
               <div class="col-md-1">
-                <input type="radio" ng-model="barcode_type" value="barcode"/>
+                <input type="radio" ng-model="barcode_type" value="barcode" id="bc_radio"/>
               </div>
               <div class="col-md-4">
-                [% l('Item Barcode:') %]
+                <label style="font-weight:normal !important;" for="bc_radio">[% l('Item Barcode:') %]</label>
               </div>
               <div class="col-md-7">
                 <input id="co_barcode"
 
             <div class="row">
               <div class="col-md-1">
-                <input type="radio" ng-model="barcode_type" value="noncat"/>
+                <input type="radio" ng-model="barcode_type" value="noncat" id="nc_radio"/>
               </div>
               <div class="col-md-4">
-                [% l('Non-cataloged Type:') %]
+                <label style="font-weight:normal !important;" for="nc_radio">[% l('Non-cataloged Type:') %]</label>
               </div>
               <div class="col-md-5">
                 <select
               <div class="col-md-6">
                 <input id="do_print_co" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_co">[% l('Print receipt') %]</label>
-                <button class="btn btn-primary pull-right" ng-disabled="notEnough('checkout')" ng-click="add('checkout')">[% l('Checkout') %]</button>
+                <button class="btn btn-primary pull-right" ng-disabled="notEnough('checkout')" ng-click="add('checkout','co_barcode')">[% l('Checkout') %]</button>
               </div>
             </div>
 
                 [% l('Due Date:') %]
               </div>
               <div class="col-md-4">
-                <eg-date-input ng-model="shared.due_date"></eg-date-input>
+                <eg-date-input ng-model="shared.due_date" min-date="minDate"></eg-date-input>
               </div>
               <div class="col-md-3">
                 <select class="form-control" ng-model="shared.due_date_offset" ng-change="resetDueDate()">
               <div class="col-md-6">
                 <input id="do_print_r" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_r">[% l('Print receipt') %]</label>
-                <button class="btn btn-primary pull-right" ng-disabled="notEnough('renew')" ng-click="add('renew')">[% l('Renew') %]</button>
+                <button class="btn btn-primary pull-right" ng-disabled="notEnough('renew')" ng-click="add('renew','re_barcode')">[% l('Renew') %]</button>
               </div>
             </div>
 
               <div class="col-md-6">
                 <input id="do_print_ihu" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_ihu">[% l('Print receipt') %]</label>
-                <button class="btn btn-primary pull-right" ng-disabled="notEnough('in_house_use')" ng-click="add('in_house_use')">[% l('Record Use') %]</button>
+                <button class="btn btn-primary pull-right" ng-disabled="notEnough('in_house_use')" ng-click="add('in_house_use','ihu_barcode')">[% l('Record Use') %]</button>
               </div>
             </div>
 
                 [% l('Checkin Date:') %]
               </div>
               <div class="col-md-6">
-                <eg-date-input ng-model="checkin.backdate"></eg-date-input>
+                <eg-date-input ng-model="checkin.backdate" min-date="minDate"></eg-date-input>
               </div>
             </div>
 
                 [% l('Item Barcode:') %]
               </div>
               <div class="col-md-6">
-                <input class="form-control" type="text" ng-model="checkin.barcode" eg-enter="!notEnough('checkin') && add('checkin')"/>
+                <input id="ci_barcode" class="form-control" type="text" ng-model="checkin.barcode" eg-enter="!notEnough('checkin') && add('checkin')"/>
               </div>
             </div>
 
               <div class="col-md-6">
                 <input id="do_print_ci" type="checkbox" ng-model="do_print" ng-click="changePrint()"></input>
                 <label for="do_print_ci">[% l('Print receipt') %]</label>
-                <button class="btn btn-primary pull-right" ng-disabled="notEnough('checkin')" ng-click="add('checkin')">[% l('Checkin') %]</button>
+                <button class="btn btn-primary pull-right" ng-disabled="notEnough('checkin')" ng-click="add('checkin','ci_barcode')">[% l('Checkin') %]</button>
                     
               </div>
             </div>
                         <td>
                           <button
                             class="btn btn-info btn-xs"
-                            ng-disabled="!logged_in || pending_xacts.length == 0"
+                            ng-disabled="!logged_in || pending_xacts.length == 0 || ses.end_time"
                             ng-click="uploadPending(ses, $index)"
                           >[% l('Upload') %]</button>
                           <button
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-tablesort.js"></script>
 <script>
 angular.module('egCoreMod').run(['egStrings', function(s) {
+  s.OFFLINE_BLOCKLIST_SUCCESS = "[% l('Offline blocklist downloaded') %]";
+  s.OFFLINE_BLOCKLIST_FAIL = "[% l('Error downloading offline blocklist') %]";
+  s.DUPLICATE_BARCODE = "[% l('Duplicate item barcode') %]";
   s.REG_ADDR_TYPE = "[% l('Mailing') %]";
   s.REG_INVALID_FIELDS =
     "[% l('Please enter valid values for all required fields.') %]"
index 8926e0d..555afbd 100644 (file)
@@ -8,6 +8,7 @@
         ng-show="!hideDatePicker"
         uib-datepicker-popup="{{date_format}}"
         is-open="datePickerIsOpen"
+        datepicker-options="options"
         ng-model="ngModel"
         ng-change="ngChange"
         ng-blur="ngBlur"
index c29b393..58ea2a1 100644 (file)
@@ -38,8 +38,8 @@ function($routeProvider , $locationProvider , $compileProvider) {
 }])
 
 .controller('OfflineSessionCtrl', 
-           ['$scope','$location','$window','egCore','$routeParams','$http','$q','$timeout','egPromptDialog','ngToast',
-    function($scope , $location , $window , egCore , $routeParams , $http , $q , $timeout , egPromptDialog , ngToast) {
+           ['$scope','$location','$window','egCore','$routeParams','$http','$q','$timeout','egPromptDialog','ngToast','egProgressDialog',
+    function($scope , $location , $window , egCore , $routeParams , $http , $q , $timeout , egPromptDialog , ngToast , egProgressDialog) {
         $scope.active_session_tab = 'pending';
 
         $scope.lookupNoncatTypeName = function (type) {
@@ -89,6 +89,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
 
         $scope.processSession = function (s, ind) {
             return $scope.setSession(s, ind).then(function() {
+                egProgressDialog.open();
 
                 return $http.get(
                     formURL({action:'execute',seskey:$scope.current_session.key})
@@ -96,8 +97,11 @@ function($routeProvider , $locationProvider , $compileProvider) {
                     if (res.data.ilsevent == "0") return $q.when(res.data.payload);
                     return $q.reject();
                 }).then(function () {
-                    return $scope.refreshSessions();
+                    egProgressDialog.close();
+                    return $scope.refreshSessions()
+                        .then(function(){ return $scope.refreshExceptions(s) });
                 },function () {
+                    egProgressDialog.close();
                     return $scope.refreshSessions().then(function() {
                         ngToast.warning(egCore.strings.OFFLINE_SESSION_PROCESSING_FAILED);
                     });
@@ -147,9 +151,15 @@ function($routeProvider , $locationProvider , $compileProvider) {
             });
         }
 
+        $scope.reprintLast = function () {
+            egCore.print.reprintLast();
+        }
+
+
         $scope.uploadPending = function (s, ind) {
             return $scope.setSession(s, ind).then(function() {
 
+                egProgressDialog.open();
                 return $scope.createOfflineXactBlob().then(function(blob) {
 
                     var form = new FormData();
@@ -169,6 +179,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
                             headers: {'Content-Type': undefined}
                         }
                     ).then(function(res) {
+                        egProgressDialog.close();
                         if (res.data.ilsevent == "0") {
                             return $scope.clear_pending(true).then(function() {
                                 return $scope.refreshSessions();
@@ -177,7 +188,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
                             ngToast.warning(egCore.strings.OFFLINE_SESSION_UPLOAD_FAILED);
                             return $scope.refreshSessions();
                         }
-                    });
+                    },function () { egProgressDialog.close() });
                 });
             });
         }
@@ -243,8 +254,10 @@ function($routeProvider , $locationProvider , $compileProvider) {
     function($q , $scope , $location , $window , egCore , egLovefield , $routeParams , $timeout , $http , ngToast , egConfirmDialog) {
         $scope.active_tab = $routeParams.tab || 'checkout';
 
+        $scope.minDate = new Date();
         $scope.blocked_patron = null;
         $scope.bad_barcode = null;
+        $scope.barcode_type = 'barcode';
         $scope.focusMe = true;
         $scope.shared = { due_date : null, due_date_offset : '' };
         $scope.workstation_obj = null;
@@ -254,6 +267,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
         $scope.org = null;
         $scope.do_print = Boolean($scope.active_tab == 'checkout');
         $scope.do_print_changed = false;
+        $scope.printed = false;
 
         $scope.imported_pending_xacts = { data : '' };
 
@@ -351,8 +365,14 @@ function($routeProvider , $locationProvider , $compileProvider) {
                                 var parts = l.split(' ');
                                 egLovefield.addOfflineBlock(parts[0], parts[1]);
                             });
+                            return $q.when();
+                        }).then(function(){
+                            ngToast.create(egCore.strings.OFFLINE_BLOCKLIST_SUCCESS);
                         });
                     }
+                },function(){
+                    ngToast.create(egCore.strings.OFFLINE_BLOCKLIST_FAIL);
+                    egCore.audio.play('warning.offline.blocklist_fail');
                 }
             );
         }
@@ -399,6 +419,7 @@ function($routeProvider , $locationProvider , $compileProvider) {
             }
 
             return $q.all(prints).finally(function() {
+                if (prints.length > 1) $scope.printed = true;
                 $scope.all_xact = [];
                 $scope.xact_page = { checkin:[], checkout:[], renew:[], in_house_use:[] };
                 angular.forEach(['checkout','renew'], function (xtype) {
@@ -499,23 +520,36 @@ function($routeProvider , $locationProvider , $compileProvider) {
             if (xtype=="in_house_use") $scope[xtype].count = 1;
         }
 
-        $scope.add = function (xtype) {
+        $scope.add = function (xtype,next_focus) {
 
-            var pbarcode = $scope[xtype].patron_barcode;
+            var barcode = $scope[xtype].barcode;
+            if (barcode) {
+                if ($scope.xact_page[xtype].filter(function(x){ return x.barcode == barcode }).length > 0) {
+                    ngToast.warning(egCore.strings.DUPLICATE_BARCODE);
+                    egCore.audio.play('warning.offline.duplicate_barcode');
+                    $scope[xtype].barcode = '';
+                    if (next_focus) $('#'+next_focus).focus();
+                    return;
+                }
+            }
 
+            var pbarcode = $scope[xtype].patron_barcode;
             if (pbarcode) {
                 egLovefield.testOfflineBlock(pbarcode).then(function (blocked) {
                     if (blocked) {
                         $scope.blocked_patron = xtype;
                         ngToast.warning(egCore.strings.PATRON_BLOCKED);
                         egCore.audio.play('warning.offline.blocked_patron');
+                        if (next_focus) $('#'+next_focus).focus();
                         return;
                     }
                     $scope.blocked_patron = null;
                     _add_impl(xtype,true)
+                    if (next_focus) $('#'+next_focus).focus();
                 });
             } else {
                 _add_impl(xtype);
+                if (next_focus) $('#'+next_focus).focus();
             }
         }
 
index 6e5932e..3a1185d 100644 (file)
@@ -707,6 +707,8 @@ function($window , egStrings) {
                 ngModel : '=',
                 ngChange : '=',
                 ngBlur : '=',
+                minDate : '=',
+                maxDate : '=',
                 ngDisabled : '=',
                 ngRequired : '=',
                 hideDatePicker : '=',
@@ -715,6 +717,12 @@ function($window , egStrings) {
             require: 'ngModel',
             templateUrl: './share/t_datetime',
             replace: true,
+            controller : ['$scope', function($scope) {
+                $scope.options = {
+                    minDate : $scope.minDate,
+                    maxDate : $scope.maxDate
+                }
+            }],
             link : function(scope, elm, attrs) {
                 if (!scope.closeText)
                     scope.closeText = egStrings.EG_DATE_INPUT_CLOSE_TEXT;