LP#1549505: add admin interface to manage badges
authorGalen Charlton <gmc@esilibrary.com>
Wed, 24 Feb 2016 22:19:14 +0000 (17:19 -0500)
committerKathy Lussier <klussier@masslnc.org>
Fri, 29 Jul 2016 20:56:11 +0000 (16:56 -0400)
Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/templates/staff/admin/local/rating/badge.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/local/rating/edit_badge.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/admin/local/t_splash.tt2
Open-ILS/src/templates/staff/css/style.css.tt2
Open-ILS/web/js/ui/default/staff/admin/local/rating/badge.js [new file with mode: 0644]

index 6f86b51..9ab8eef 100644 (file)
@@ -304,7 +304,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Importance Horizon" name="importance_age" reporter:datatype="text"/>
                        <field reporter:label="Importance Interval" name="importance_interval" reporter:datatype="text"/>
                        <field reporter:label="Importance Scale" name="importance_scale" reporter:datatype="text"/>
-                       <field reporter:label="Percentile" name="percentile" reporter:datatype="int"/>
+                       <field reporter:label="Percentile" name="percentile" reporter:datatype="float"/>
                        <field reporter:label="Attribute Filter" name="attr_filter" reporter:datatype="text"/>
                        <field reporter:label="Circ Mod Filter" name="circ_mod_filter" reporter:datatype="link"/>
                        <field reporter:label="Bib Source Filter" name="src_filter" reporter:datatype="link"/>
diff --git a/Open-ILS/src/templates/staff/admin/local/rating/badge.tt2 b/Open-ILS/src/templates/staff/admin/local/rating/badge.tt2
new file mode 100644 (file)
index 0000000..a67249d
--- /dev/null
@@ -0,0 +1,60 @@
+[%
+  WRAPPER 'staff/base.tt2';
+  ctx.page_type = l('Statistical Popularity Badges');
+  ctx.page_app = 'egAdminRating';
+  ctx.page_ctrl = 'Badges';
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/local/rating/badge.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+<script>
+  angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.CONFIRM_DELETE_BADGE_TITLE =
+      "[% l('Confirm badge deletion') %]";
+    s.CONFIRM_DELETE_BADGE_BODY =
+      "[% l('Delete badge {{id}} ({{name}})?') %]";
+  }]);
+</script>
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Statistical Popularity Badges') %]
+  </div>
+</div>
+
+<!--
+<div class="row">
+  <div class="col-md-4">
+    <div class="form-group">
+      <label>[% l('Scope Library') %]</label>
+      <eg-org-selector onchange="org_changed" 
+        selected="context_org"></eg-org-selector>
+    </div>
+  </div>
+</div>
+-->
+
+<eg-grid
+    id-field="id"
+    idl-class="rb"
+    grid-controls="gridControls"
+    features="-multiselect"
+    persist-key="admin.local.rating.badge">
+   
+    <eg-grid-action label="[% l('Add badge') %]" handler="create_rb"></eg-grid-action> 
+    <eg-grid-action label="[% l('Edit badge') %]" handler="update_rb"></eg-grid-action> 
+    <eg-grid-action label="[% l('Delete badge') %]" handler="delete_rb"></eg-grid-action> 
+
+    <eg-grid-field label="[% l('Name') %]"                   path='name'></eg-grid-field>
+    <eg-grid-field label="[% l('Description') %]"            path='description'></eg-grid-field>
+    <eg-grid-field label="[% l('Scope') %]"                  path='scope.name'></eg-grid-field>
+    <eg-grid-field label="[% l('Popularity Parameter') %]"   path='popularity_parameter.name'></eg-grid-field>
+    <eg-grid-field label="[% l('ID') %]" path='id' required hidden></eg-grid-field>
+    <eg-grid-field path='*' hidden></eg-grid-field> 
+</eg-grid>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/local/rating/edit_badge.tt2 b/Open-ILS/src/templates/staff/admin/local/rating/edit_badge.tt2
new file mode 100644 (file)
index 0000000..9a1426b
--- /dev/null
@@ -0,0 +1,42 @@
+<form ng-submit="ok(record)" role="form" class="form-validated">
+    <div class="modal-header">
+      <button type="button" class="close" ng-click="cancel()" 
+        aria-hidden="true">&times;</button>
+      <h4 class="modal-title">{{record_label}}</h4>
+    </div>
+    <div class="modal-body">
+      <div class="form-group row" ng-repeat="field in fields | filter:{virtual:'!true'}">
+        <div class="col-md-3">
+          <label for="rec-{{field.name}}">{{field.label}}</label>
+        </div>
+        <div class="col-md-9">
+            <span  ng-if="field.datatype == 'id' || field.readonly">{{record[field.name]}}</span>
+            <eg-org-selector ng-if="field.datatype == 'org_unit'"
+                selected="record[field.name + '_ou'].org" 
+                onchange="record[field.name + '_ou'].update_org">
+            </eg-org-selector>
+            <input ng-if="field.datatype == 'text'"
+                ng-required="field.is_required(record)"
+                ng-model="record[field.name]"></input>
+            <input ng-if="field.datatype == 'int'" type="number"
+                ng-required="field.is_required(record)"
+                ng-model="record[field.name]"></input>
+            <input ng-if="field.datatype == 'float'" type="number" step="0.1"
+                ng-required="field.is_required(record)"
+                ng-model="record[field.name]"></input>
+            <span ng-if="field.datatype == 'link'" class="nullable">
+            <select ng-if="field.datatype == 'link'"
+                ng-options="item.id as item.name for item in field.linked_values"
+                ng-model="record[field.name]">
+                    <option value=""></option>
+                </select>
+            </span>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <input type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
+      <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
+    </div>
+  </div> <!-- modal-content -->
+</form>
index 8dcb28f..f6b7da3 100644 (file)
@@ -31,6 +31,7 @@
     ,[ l('Search Filter Groups'), "./admin/local/actor/search_filter_group" ]
     ,[ l('Standing Penalties'), "./admin/local/config/standing_penalty" ]
     ,[ l('Statistical Categories Editor'), "./admin/local/asset/stat_cat_editor" ]
+    ,[ l('Statistical Popularity Badges'), "./admin/local/rating/badge" ]
    ];
 
    USE table(interfaces, rows=9);
index c2ff924..d4e4ae5 100644 (file)
@@ -97,6 +97,9 @@
 .form-validated input.ng-valid.ng-dirty {
   background-color: #78FA89;
 }
+.form-validated input.ng-invalid-required {
+  background-color: #FACDCF;
+}
 
 /* --------------------------------------------------------------------------
  * Local style
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/rating/badge.js b/Open-ILS/web/js/ui/default/staff/admin/local/rating/badge.js
new file mode 100644 (file)
index 0000000..3df43e9
--- /dev/null
@@ -0,0 +1,189 @@
+angular.module('egAdminRating',
+    ['ngRoute','ui.bootstrap','egCoreMod','egUiMod','egGridMod'])
+
+.controller('Badges',
+       ['$scope','$q','$timeout','$location','$window','$modal',
+        'egCore','egGridDataProvider','egConfirmDialog',
+function($scope , $q , $timeout , $location , $window , $modal ,
+         egCore , egGridDataProvider , egConfirmDialog) {
+
+    egCore.startup.go();
+
+    function get_record_label() {
+        return egCore.idl.classes['rb'].label;
+    }
+
+    function flatten_linked_values(cls, list) {
+        var results = [];
+        var fields = egCore.idl.classes[cls].fields;
+        var id_field;
+        var selector;
+        angular.forEach(fields, function(fld) {
+            if (fld.datatype == 'id') {
+                id_field = fld.name;
+                selector = fld.selector ? fld.selector : id_field;
+                return;
+            }
+        });
+        angular.forEach(list, function(item) {
+            var rec = egCore.idl.toHash(item);
+            results.push({
+                id : rec[id_field],
+                name : rec[selector]
+            });
+        });
+        return results;
+    }
+
+    horizon_required = {};
+    percentile_required = {};
+    function get_popularity_with_required() {
+        egCore.pcrud.retrieveAll(
+            'rp', {}, {atomic : true}
+        ).then(function(list) {
+            angular.forEach(list, function(item) {
+                horizon_required[item.id()] = item.require_horizon();
+                percentile_required[item.id()] = item.require_percentile();
+            });
+        });
+    }
+
+    function get_field_list(rec) {
+        var fields = egCore.idl.classes['rb'].fields;
+
+        var promises = [];
+        // flesh selectors
+        angular.forEach(fields, function(fld) {
+            if (fld.datatype == 'link') {
+                egCore.pcrud.retrieveAll(
+                    fld.class, {}, {atomic : true}
+                ).then(function(list) {
+                    fld.linked_values = flatten_linked_values(fld.class, list);
+                });
+            }
+            if (fld.datatype == 'org_unit') {
+                rec[fld.name + '_ou'] = {};
+                rec[fld.name + '_ou']['org'] = egCore.org.get(rec[fld.name]);
+                rec[fld.name + '_ou']['update_org'] = function(org) {
+                    rec[fld.name] = org.id();
+                };
+            }
+            if (fld.name == 'last_calc') {
+                fld['readonly'] = true;
+            }
+            fld.is_required = function(record) {
+                return false;
+            };
+            if (fld.name == 'name') {
+                fld.is_required = function(record) {
+                    return true;
+                };
+            }
+            if (fld.name == 'horizon_age') {
+                fld.is_required = function(record) {
+                    return horizon_required[record['popularity_parameter']] == 't';
+                };
+            }
+            if (fld.name == 'percentile') {
+                fld.is_required = function(record) {
+                    return percentile_required[record['popularity_parameter']] == 't';
+                };
+            }
+        });
+        return fields;
+    }
+
+    function spawn_editor(rb, action) {
+        var deferred = $q.defer();
+        $modal.open({
+            templateUrl: './admin/local/rating/edit_badge',
+            controller:
+                ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                $scope.focusMe = true;
+                $scope.record = egCore.idl.toHash(rb);
+                // non-integer numeric field require parseFloat
+                $scope.record.percentile = parseFloat($scope.record.percentile);
+                $scope.record_label = get_record_label();
+                $scope.fields = get_field_list($scope.record);
+                get_popularity_with_required();
+                $scope.ok = function(args) { $modalInstance.close(args) }
+                $scope.cancel = function () { $modalInstance.dismiss() }
+            }]
+        }).result.then(function(args) {
+            var rb = new egCore.idl.rb();
+            if (action == 'update') rb.id(args.id);
+            rb.name(args.name);
+            rb.description(args.description);
+            rb.scope(args.scope);
+            rb.weight(args.weight);
+            rb.horizon_age(args.horizon_age);
+            rb.importance_age(args.importance_age);
+            rb.importance_interval(args.importance_interval);
+            rb.importance_scale(args.importance_scale);
+            rb.recalc_interval(args.recalc_interval);
+            rb.attr_filter(args.attr_filter);
+            rb.src_filter(args.src_filter);
+            rb.circ_mod_filter(args.circ_mod_filter);
+            rb.loc_grp_filter(args.loc_grp_filter);
+            rb.popularity_parameter(args.popularity_parameter);
+            rb.fixed_rating(args.fixed_rating);
+            rb.percentile(args.percentile);
+            rb.discard(args.discard);
+            rb.last_calc(args.last_calc);
+            if (action == 'create') {
+                egCore.pcrud.create(rb).then(function() { deferred.resolve(); });
+            } else {
+                egCore.pcrud.update(rb).then(function() { deferred.resolve(); });
+            }
+        });
+        return deferred.promise;
+    }
+
+    $scope.create_rb = function() {
+        var rb = new egCore.idl.rb();
+
+        // make sure an OU is selected by default
+        rb.scope(egCore.auth.user().ws_ou());
+
+        spawn_editor(rb, 'create').then(function() {
+            $scope.gridControls.refresh();
+        });
+    }
+
+    $scope.update_rb = function(selected) {
+        if (!selected || !selected.length) return;
+
+        egCore.pcrud.retrieve('rb', selected[0].id).then(function(rb) {
+            spawn_editor(rb, 'update').then(function() {
+                $scope.gridControls.refresh();
+            });
+        });
+    }
+
+    $scope.delete_rb = function(selected) {
+        if (!selected || !selected.length) return;
+
+        egCore.pcrud.retrieve('rb', selected[0].id).then(function(rb) {
+            egConfirmDialog.open(
+                egCore.strings.CONFIRM_DELETE_BADGE_TITLE,
+                egCore.strings.CONFIRM_DELETE_BADGE_BODY,
+                { name : rb.name(), id : rb.id() }
+            ).result.then(function() {
+                egCore.pcrud.remove(rb).then(function() {
+                    $scope.gridControls.refresh();
+                });
+            });            
+        });
+    }
+
+    $scope.gridControls = {
+        activateItem : function (item) {
+            $scope.update_rb([item]);
+        },
+        setQuery : function() {
+            return {
+                'id' : {'!=' : null}
+            }
+        }
+    }
+}])