Angular User Feedback

by AntonLapshin

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.11/angular.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.11/angular-animate.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-smart-table/2.1.3/smart-table.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/less.js/1.6.3/less.min.js"></script>
<section ng-app="app" ng-controller="OrderOverviewController">
  <h1>
    Feedback
  </h1>
  <div class="table-container">
    <table st-table="displayedCollection" class="table table-striped user-feedback" st-pagination-scroll st-safe-src="rowCollection">
      <thead>
        <tr class="header">
          <th st-sort="user">User</th>
          <th st-sort="deceased_name">Customer</th>
          <th st-sort="created_at" st-sort-default="reverse">Date</th>
          <th st-sort="score">Score</th>
          <th st-sort="attention">Attention</th>
        </tr>
        <tr>
          <th>
            <input st-search="user" placeholder="Search for user" class="input-sm form-control" type="search" />
          </th>
          <th colspan="4">
            <input st-search placeholder="Global search" class="input-sm form-control" type="search" />
          </th>
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat-start="row in displayedCollection" st-select-row="row" class="{{ 'attention-' + row.attention }}">
          <td>{{row.user}}</td>
          <td>{{row.deceased_name | uppercase}}</td>
          <td>{{row.created_at | date: 'yyyy-MM-dd'}}</td>
          <td>
            <feedback-rating ng-init="init(row.score)"></feedback-rating>
          </td>
          <td>
            <feedback-attention ng-init="init(row.attention)"></feedback-attention>
          </td>
        </tr>
        <tr...

CSS

h1 {
  text-align: center;
}

.table-container {
  max-height: 400px;
  overflow-y: auto;
  overflow-x: hidden;
  position: relative;
  border: 1px solid #eee;
  h1,
  h2,
  h3,
  h4,
  .col-xs-4 {
    text-align: center;
  }
}

.st-sort-ascent:before {
  content: '\25B2';
}

.st-sort-descent:before {
  content: '\25BC';
}

.user-feedback {
  th,
  tr {
    cursor: pointer;
  }
  thead {
    background-color: #eee;
  }
  tbody tr {
    transition: all ease 400ms;
  }
  tbody {
    tr {
      &:not(.tigerbox) {
        td:first-child {
          position: relative;
          padding-left: 10px;
          &:before {
            transition: all ease 400ms;
            content: '';
            position: absolute;
            width: 0;
            height: 100%;
            left: 0;
            top: 0;
            background-color: #1c90f3 !important;
            z-index: 1;
          }
        }
        &:hover,
        &.st-selected {
          td:first-child {
            &:before {
              width: 5px;
            }
          }
        }
        &.attention-1 {
          //background-color: #a4ef8c;
        }
        &.attention-2 {
          //background-color: orange;
        }
        &.attention-3 {
          //background-color: tomato;
        }
      }
    }
    .improvements {
      margin-top: 2em;
    }
  }
  .tigerbox {
    background-color: white !important;
  }
}


/* The starting CSS styles for the enter animation */

.fade2.ng-enter {
  transition: 0.5s linear all;
  opacity: 0;
}


/* The finishing CSS styles for the enter animation */

.fade2.ng-enter.ng-enter-active {
  opacity: 1;
}


/* Rating */

.rating {
  direction: rtl;
  padding-right: 0;
  margin: 0;
  min-width: 122px;
  display: inline-block;
  vertical-align: middle;
}

.rating__star {
  cursor: pointer;
  display: inline-block;
  font-size: 22px;
  height: 24px;
  position: relative;
  width: 24px;
}

.rating__star::before {
  color: #34495e;
  content: "\2605";
  line-height:...

JavaScript

$('head style[type="text/css"]').attr('type', 'text/less');
less.env = 'development';
less.refreshStyles();

(function() {

  var app = angular.module('app', ['smart-table', 'ngAnimate']);

  app.directive('feedbackRating', function() {
    return {
      'restrict': 'E',
      scope: true,
      'template': '<ol class="rating"><li ng-repeat="star in stars | reverse" class="rating__star" ng-class="{ is_active: rating >= star }"></li></ol>',
      'controller': function($scope) {
        $scope.stars = [1, 2, 3, 4, 5];
        $scope.rating = 0;
        $scope.init = function(score) {
          $scope.rating = score;
        }
      }
    };
  });

  app.directive('feedbackAttention', function() {
    return {
      restrict: 'E',
      scope: true,
      template: '<svg ng-show="isShown" fill="#000000" height="24"  viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path class="color-path" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
      controller: function($scope) {
        $scope.isShown = false;
        $scope.init = function(attention) {
          $scope.isShown = attention === 1;
        }
      }
    };
  });

  app.filter('reverse', function() {
    return function(items) {
      return items.slice().reverse();
    };
  });

  angular.module('smart-table')
    .directive('stPaginationScroll', ['$timeout', function(timeout) {
      return {
        require: 'stTable',
        link: function(scope, element, attr, ctrl) {
          var itemByPage = 20;
          var pagination = ctrl.tableState().pagination;
          var lengthThreshold = 50;
          var timeThreshold = 400;
          var handler = function() {
            scope.callServer();
          };
          var promise = null;
          var lastRemaining = 9999;
          var container = angular.element(element.parent());

          container.bind('scroll', function() {
            var remaining = container[0].scrollHeight -...