dashboard-page.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. const app = require('../app')
  2. const moment = require('moment-immutable')
  3. const _ = require('lodash')
  4. const { previousWeekIcon, previousDayIcon, nextDayIcon, nextWeekIcon } = require('../assets')
  5. app.component('appDashboardPage', {
  6. template: html`
  7. <app-user-area title-text="Dashboard">
  8. <div>
  9. <md-button
  10. class="md-fab"
  11. ng-click="$ctrl.setOffset($ctrl.offset - 7)">
  12. <md-icon md-svg-src="${previousWeekIcon}"></md-icon>
  13. </md-button>
  14. <md-button
  15. class="md-fab"
  16. ng-click="$ctrl.setOffset($ctrl.offset - 1)">
  17. <md-icon md-svg-src="${previousDayIcon}"></md-icon>
  18. </md-button>
  19. <span
  20. ng-repeat="workday in $ctrl.workdays track by workday.formatted"
  21. ng-init="
  22. invalid = workday.hasData && (!workday.hasLabor || !workday.hasService);
  23. valid = workday.hasData && workday.hasLabor && workday.hasService;
  24. tooltip = workday.hasData
  25. ? (
  26. workday.hasLabor && workday.hasService
  27. ? ''
  28. : workday.hasLabor
  29. ? workday.formatted + ' has Labor data, but no Service data.'
  30. : workday.hasService
  31. ? workday.formatted + ' has Service data, but no Service data.'
  32. : workday.formatted + ' is missing Labor and Service data.'
  33. ): 'No data exists for ' + workday.formatted + '.';"
  34. title="{{tooltip}}"
  35. ng-click="tooltip && $ctrl.$mdToast.showSimple(tooltip)">
  36. <md-button
  37. style="min-width: auto; width: 3em;"
  38. class="md-raised md-mini"
  39. ng-class="{
  40. 'md-primary': workday.date.valueOf() == $ctrl.date.valueOf()
  41. }"
  42. ng-disabled="!valid"
  43. ng-click="$ctrl.setDate(workday.date)"
  44. md-badge="{{
  45. invalid
  46. ? '!'
  47. : ''
  48. }}">
  49. {{workday.short}}
  50. </md-button>
  51. </span>
  52. <md-button
  53. class="md-fab"
  54. ng-click="$ctrl.setOffset($ctrl.offset + 1)">
  55. <md-icon md-svg-src="${nextDayIcon}"></md-icon>
  56. </md-button>
  57. <md-button
  58. class="md-fab"
  59. ng-click="$ctrl.setOffset($ctrl.offset + 7)">
  60. <md-icon md-svg-src="${nextWeekIcon}"></md-icon>
  61. </md-button>
  62. </div>
  63. <h2>{{$ctrl.date.format('LL')}}</h2>
  64. <div flex layout="row" ng-show="$ctrl.statistics">
  65. <md-card flex ng-repeat="serviceCategory in $ctrl.statistics.serviceCategories">
  66. <md-card-content>
  67. <h2>{{::$ctrl.serviceCategories[serviceCategory.key].name}}</h2>
  68. <div flex layout="row">
  69. <md-card flex="30" md-whiteframe="15">
  70. <md-card-title><md-card-title-text style="text-align: center">
  71. Cartons
  72. </md-card-title-text></md-card-title>
  73. <md-card-content>
  74. <h1 style="text-align: center">{{serviceCategory.lastMetrics[serviceCategory.serviceColumn] || 0}}</h1>
  75. </md-card-content>
  76. </md-card>
  77. <md-card flex="30" md-whiteframe="15">
  78. <md-card-title><md-card-title-text style="text-align: center">
  79. Labor Cost
  80. </md-card-title-text></md-card-title>
  81. <md-card-content>
  82. <h1 ng-if="serviceCategory.lastMetrics.laborCost" style="text-align: center">$\{{Math.floor(serviceCategory.lastMetrics.laborCost) || null}}</h1>
  83. <h1 ng-if="!serviceCategory.lastMetrics.laborCost" style="text-align: center">N/A</h1>
  84. </md-card-content>
  85. </md-card>
  86. <md-card flex="30" md-whiteframe="15">
  87. <md-card-title><md-card-title-text style="text-align: center">
  88. Cost per Carton
  89. </md-card-title-text></md-card-title>
  90. <md-card-content>
  91. <h1 ng-if="serviceCategory.lastMetrics.costPer" style="text-align: center">{{serviceCategory.lastMetrics.costPer | currency}}</h1>
  92. <h1 ng-if="!serviceCategory.lastMetrics.costPer" style="text-align: center">N/A</h1>
  93. </md-card-content>
  94. </md-card>
  95. </div>
  96. <div ng-repeat="terminal in serviceCategory.terminals">
  97. <table md-table>
  98. <thead>
  99. <th align="left">
  100. <h4>{{::$ctrl.terminals[terminal.terminal].name}}</h4>
  101. </th>
  102. <th align="right">
  103. <span ng-if="$index == 0">Cost per Carton</span>
  104. </th>
  105. </thead>
  106. <tbody>
  107. <tr ng-repeat="laborCategory in terminal.laborCategories">
  108. <th align="left">{{::$ctrl.laborCategories[laborCategory.laborCategory].name}}</th>
  109. <td align="right">{{laborCategory.costPer | currency}}</td>
  110. </tr>
  111. <tr ng-if="terminal.laborCategories.length > 1">
  112. <th align="left">All</th>
  113. <td align="right">{{terminal.costPer | currency}}</td>
  114. </tr>
  115. </tbody>
  116. </table>
  117. <hr />
  118. </div>
  119. <div ng-if="serviceCategory.terminals.length > 1">
  120. <table md-table>
  121. <thead>
  122. <th align="left">
  123. <h4>Overall</h4>
  124. </th>
  125. <th align="right"></th>
  126. </thead>
  127. <tbody>
  128. <tr ng-repeat="laborCategory in serviceCategory.laborCategories">
  129. <th align="left">{{::$ctrl.laborCategories[laborCategory.laborCategory].name}}</th>
  130. <td align="right">{{laborCategory.costPer | currency}}</td>
  131. </tr>
  132. <tr ng-if="serviceCategory.laborCategories.length > 1">
  133. <th align="left">All</th>
  134. <td align="right">{{serviceCategory.costPer | currency}}</td>
  135. </tr>
  136. </tbody>
  137. </table>
  138. </div>
  139. <md-card>
  140. <md-card-title>
  141. <md-card-title-text>Labor cost</md-card-title-text>
  142. </md-card-title>
  143. <md-card-content>
  144. <div style="width: 520px; height: 260px;" >
  145. <canvas class="chart chart-line" chart-data="serviceCategory.charts.laborCost.data" chart-colors="serviceCategory.charts.laborCost.colors"
  146. chart-labels="serviceCategory.charts.laborCost.labels" chart-series="serviceCategory.charts.laborCost.series" chart-options="serviceCategory.charts.laborCost.options">
  147. </canvas>
  148. </div>
  149. <div ng-repeat="color in serviceCategory.charts.laborCost.colors">
  150. <div style="background-color: {{color}}; width: 16px; height: 16px; display: inline-block; margin: 0 4px;"></div>
  151. {{$ctrl.charts.costPerCarton.series[$index]}}
  152. </div>
  153. </md-card-content>
  154. </md-card>
  155. <md-card>
  156. <md-card-title>
  157. <md-card-title-text>Cost per carton</md-card-title-text>
  158. </md-card-title>
  159. <md-card-content>
  160. <div style="width: 520px; height: 260px;" >
  161. <canvas class="chart chart-line" chart-data="serviceCategory.charts.costPerCarton.data" chart-colors="serviceCategory.charts.costPerCarton.colors"
  162. chart-labels="serviceCategory.charts.costPerCarton.labels" chart-series="serviceCategory.charts.costPerCarton.series" chart-options="serviceCategory.charts.costPerCarton.options">
  163. </canvas>
  164. </div>
  165. <div ng-repeat="color in serviceCategory.charts.costPerCarton.colors">
  166. <div style="background-color: {{color}}; width: 16px; height: 16px; display: inline-block; margin: 0 4px;"></div>
  167. {{$ctrl.charts.costPerCarton.series[$index]}}
  168. </div>
  169. </md-card-content>
  170. </md-card>
  171. </md-card-content>
  172. </md-card>
  173. </div>
  174. <div flex layout="column" ng-show="$ctrl.charts" layout-gt-sm="row">
  175. <md-card flex="50" class="mg-margin md-padding">
  176. <md-card-title>
  177. <md-card-title-text>
  178. <span class="md-headline">Labor Costs</span>
  179. <span class="md-subhead"></span>
  180. </md-card-title-text>
  181. </md-card-title>
  182. <md-card-content>
  183. <div style="width: 520px; height: 260px;" >
  184. <canvas class="chart chart-line" chart-data="$ctrl.charts.laborCost.data" chart-colors="$ctrl.charts.laborCost.colors"
  185. chart-labels="$ctrl.charts.laborCost.labels" chart-series="$ctrl.charts.laborCost.series" chart-options="$ctrl.charts.laborCost.options">
  186. </canvas>
  187. </div>
  188. <div ng-repeat="color in $ctrl.charts.laborCost.colors">
  189. <div style="background-color: {{color}}; width: 16px; height: 16px; display: inline-block; margin: 0 4px;"></div>
  190. {{$ctrl.charts.laborCost.series[$index]}}
  191. </div>
  192. </md-card-content>
  193. </md-card>
  194. <md-card flex="50" class="mg-margin md-padding">
  195. <md-card-title>
  196. <md-card-title-text>
  197. <span class="md-headline">Cost per Carton</span>
  198. <span class="md-subhead"></span>
  199. </md-card-title-text>
  200. </md-card-title>
  201. <md-card-content>
  202. <div style="width: 520px; height: 260px;" >
  203. <canvas class="chart chart-line" chart-data="$ctrl.charts.costPerCarton.data" chart-colors="$ctrl.charts.costPerCarton.colors"
  204. chart-labels="$ctrl.charts.costPerCarton.labels" chart-series="$ctrl.charts.costPerCarton.series" chart-options="$ctrl.charts.costPerCarton.options">
  205. </canvas>
  206. </div>
  207. <div ng-repeat="color in $ctrl.charts.costPerCarton.colors">
  208. <div style="background-color: {{color}}; width: 16px; height: 16px; display: inline-block; margin: 0 4px;"></div>
  209. {{$ctrl.charts.costPerCarton.series[$index]}}
  210. </div>
  211. </md-card-content>
  212. </md-card>
  213. </div>
  214. </app-user-area>
  215. `,
  216. controller: function(api, statistics, $scope, $mdToast) {
  217. this.$mdToast = $mdToast
  218. $scope.Math = Math
  219. const load = (date) => {
  220. api.statistics(date).then(stats => {
  221. this.statistics = stats
  222. stats.serviceCategories.forEach(serviceCategory => {
  223. serviceCategory.charts = {
  224. costPerCarton: statistics.chart({
  225. rows: serviceCategory.metricsOverTime,
  226. seriesField: 'key',
  227. dataField: 'costPer',
  228. labelsField: 'date',
  229. format: (label, value) => `${label}: $${value.toFixed(2)} per carton`
  230. }),
  231. laborCost: statistics.chart({
  232. rows: serviceCategory.metricsOverTime,
  233. seriesField: 'key',
  234. dataField: 'laborCost',
  235. labelsField: 'date',
  236. format: (label, value) => `${label}: $${value.toFixed(2)} labor cost`
  237. })
  238. }
  239. })
  240. this.charts = statistics.charts(stats.metricsOverTime)
  241. this.last = stats.last
  242. })
  243. }
  244. api.terminals().then(terminals => {
  245. this.terminals = terminals
  246. })
  247. api.laborCategories().then(laborCategories => {
  248. this.laborCategories = laborCategories
  249. })
  250. api.serviceCategories().then(serviceCategories => {
  251. this.serviceCategories = serviceCategories
  252. })
  253. api.get(`/api/workdays`).then(results => {
  254. const wds = {}
  255. results.workdays.forEach(wd => (wds[moment(wd.date).format('YYYY-MM-DD')] = wd))
  256. const getWorkdays = (offset) => {
  257. const dates = _.range(offset - 6, offset + 1)
  258. .map(x => {
  259. const date = moment().startOf('day').add(x, 'days')
  260. const workday = wds[date.format('YYYY-MM-DD')]
  261. return {
  262. date,
  263. formatted: date.format('L'),
  264. short: date.format('M/DD'),
  265. hasData: !!workday,
  266. hasLabor: workday && workday.hasLabor,
  267. hasService: workday && workday.hasService
  268. }
  269. })
  270. return dates
  271. }
  272. this.setOffset = (offset) => {
  273. this.offset = offset
  274. this.workdays = getWorkdays(offset)
  275. }
  276. this.setDate = (date) => {
  277. this.date = date
  278. this.now = moment()
  279. load(date)
  280. }
  281. const defaultWorkday = results.workdays.slice(0).reverse().find(x => x.hasLabor && x.hasService)
  282. if (defaultWorkday) {
  283. this.setDate(moment(defaultWorkday.date))
  284. this.setOffset(3 - (moment().diff(moment(defaultWorkday.date), 'days')))
  285. } else {
  286. this.setOffset(0)
  287. }
  288. // this.setDate(moment(
  289. // results.workdays
  290. // .map(wd => moment(wd.date).valueOf())
  291. // .sort()
  292. // .pop() || moment.now()
  293. // ))
  294. })
  295. // statistics.costPerCarton().then(statistics => {
  296. // Object.assign(this, statistics)
  297. // })
  298. }
  299. })
  300. window.moment = moment