Преглед изворни кода

Dashboard, disable seed data

Alan Colon пре 7 година
родитељ
комит
19fdfb8274

+ 5 - 1
app/assets/index.js

@@ -10,5 +10,9 @@ module.exports = Object.assign(assets, {
   clientIcon: require('@alancnet/material-design-icons/maps_ic_store_mall_directory_48px.svg'),
   serviceIcon: require('./noun_service_406879.svg'),
   calculatedIcon: require('./noun_calculate_605248.svg'),
-  dollarIcon: require('./noun_dollar_1083823.svg')
+  dollarIcon: require('./noun_dollar_1083823.svg'),
+  previousWeekIcon: require('@alancnet/icomoon-svg/backward2.svg'),
+  previousDayIcon: require('@alancnet/icomoon-svg/previous2.svg'),
+  nextDayIcon: require('@alancnet/icomoon-svg/next2.svg'),
+  nextWeekIcon: require('@alancnet/icomoon-svg/forward3.svg')
 })

+ 21 - 0
app/assets/noun_delivery van_580572.svg

@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" version="1.1" viewBox="0 0 100 125" xml:space="preserve">
+  <!-- https://thenounproject.com/search/?q=dispatch&i=580572 -->
+    <g>
+        <g>
+            <path d="M26.543,60.748c-3.834,0-6.943,3.108-6.943,6.943c0,3.834,3.109,6.943,6.943,6.943c3.835,0,6.943-3.109,6.943-6.943    C33.486,63.856,30.378,60.748,26.543,60.748z M26.543,70.528c-1.567,0-2.837-1.271-2.837-2.837c0-1.567,1.27-2.837,2.837-2.837    c1.566,0,2.838,1.27,2.838,2.837C29.381,69.258,28.109,70.528,26.543,70.528z"/>
+        </g>
+        <path d="M63.257,19.867H11.949c-2.982,0-5.401,2.735-5.401,6.027v6.902h33.616v-6.608L59.245,40.53L40.164,54.873v-6.262H6.548   v10.071c0,3.29,2.419,5.695,5.401,5.695h5.161c0.078-0.051,0.153-0.098,0.232-0.147h0.806c1.36-3.298,4.605-5.619,8.395-5.619   s7.034,2.321,8.395,5.619h28.62c2.882,0,5.236-2.555,5.392-5.698c0.003,0,0.006-0.003,0.009-0.003v-0.327V45.101v-7.282v-8.943   v-2.98C68.958,22.603,66.24,19.867,63.257,19.867z"/>
+        <g>
+            <path d="M78.372,60.748c-3.834,0-6.942,3.108-6.942,6.943c0,3.834,3.108,6.943,6.942,6.943c3.835,0,6.943-3.109,6.943-6.943    C85.315,63.856,82.207,60.748,78.372,60.748z M78.372,70.528c-1.566,0-2.836-1.271-2.836-2.837c0-1.567,1.27-2.837,2.836-2.837    c1.567,0,2.838,1.27,2.838,2.837C81.21,69.258,79.939,70.528,78.372,70.528z"/>
+        </g>
+        <g>
+            <path d="M92.158,37.271c-1.474-2.946-5.336-5.333-8.629-5.333H71.607v17.933v6.216v5.194c1.645-1.649,3.918-2.67,6.431-2.67    c3.844,0,7.128,2.391,8.451,5.767h1.593c0.01,0.021,0.021,0.04,0.03,0.06h7.339v-0.06v-8.596v-5.911V43.86L92.158,37.271z     M91.215,46.842H77.568V34.92h5.961c1.648,0,3.577,1.193,4.313,2.666l3.372,6.741V46.842z"/>
+        </g>
+    </g>
+    <!-- <text x="0" y="115" fill="#000000" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif" font-size="5px" font-weight="bold">
+        Created by Erika Carter
+    </text>
+    <text x="0" y="120" fill="#000000" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif" font-size="5px" font-weight="bold">
+        from the Noun Project
+    </text> -->
+</svg>

+ 89 - 10
app/components/dashboard-page.js

@@ -1,10 +1,48 @@
 const app = require('../app')
+const moment = require('moment-immutable')
+const _ = require('lodash')
+const { previousWeekIcon, previousDayIcon, nextDayIcon, nextWeekIcon } = require('../assets')
 
 app.component('appDashboardPage', {
   template: html`
     <app-user-area title-text="Dashboard">
 
-      <div flex layout="row">
+      <div>
+        <md-button
+          class="md-fab"
+          ng-click="$ctrl.setOffset($ctrl.offset - 7)">
+          <md-icon md-svg-src="${previousWeekIcon}"></md-icon>
+        </md-button>
+        <md-button
+          class="md-fab"
+          ng-click="$ctrl.setOffset($ctrl.offset - 1)">
+          <md-icon md-svg-src="${previousDayIcon}"></md-icon>
+        </md-button>
+        <md-button
+          style="min-width: auto; width: 3em;"
+          class="md-raised md-mini"
+          ng-repeat="workday in $ctrl.workdays track by workday.formatted"
+          ng-class="{
+            'md-primary': workday.date.valueOf() == $ctrl.date.valueOf()
+          }"
+          ng-disabled="!workday.hasData"
+          ng-click="$ctrl.setDate(workday.date)"
+          >
+          {{workday.short}}
+        </md-button>
+        <md-button
+          class="md-fab"
+          ng-click="$ctrl.setOffset($ctrl.offset + 1)">
+          <md-icon md-svg-src="${nextDayIcon}"></md-icon>
+        </md-button>
+        <md-button
+          class="md-fab"
+          ng-click="$ctrl.setOffset($ctrl.offset + 7)">
+          <md-icon md-svg-src="${nextWeekIcon}"></md-icon>
+        </md-button>
+      </div>
+      <h2>{{$ctrl.date.format('LL')}}</h2>
+      <div flex layout="row" ng-show="$ctrl.statistics">
         <md-card flex ng-repeat="serviceCategory in $ctrl.statistics.serviceCategories">
           <md-card-content>
             <h2>{{::$ctrl.serviceCategories[serviceCategory.serviceCategory].name}}</h2>
@@ -38,7 +76,7 @@ app.component('appDashboardPage', {
               <table md-table>
                 <thead>
                   <th align="left">
-                    <h3>{{::$ctrl.terminals[terminal.terminal].name}}</h3>
+                    <h4>{{::$ctrl.terminals[terminal.terminal].name}}</h4>
                   </th>
                   <th align="right">
                     <span ng-if="$index == 0">Cost per Carton</span>
@@ -61,7 +99,7 @@ app.component('appDashboardPage', {
               <table md-table>
                 <thead>
                   <th align="left">
-                    <h3>Overall</h3>
+                    <h4>Overall</h4>
                   </th>
                   <th align="right"></th>
                 </thead>
@@ -80,7 +118,7 @@ app.component('appDashboardPage', {
           </md-card-content>
         </md-card>
       </div>
-      <div flex layout="column" layout-gt-sm="row">
+      <div flex layout="column" ng-show="$ctrl.charts" layout-gt-sm="row">
         <md-card flex="50" class="mg-margin md-padding">
           <md-card-title>
             <md-card-title-text>
@@ -124,11 +162,13 @@ app.component('appDashboardPage', {
   `,
   controller: function(api, statistics, $scope) {
     $scope.Math = Math
-    api.statistics().then(stats => {
-      this.statistics = stats
-      this.charts = statistics.charts(stats.metricsOverTime)
-      this.last = stats.last
-    })
+    const load = (date) => {
+      api.statistics(date).then(stats => {
+        this.statistics = stats
+        this.charts = statistics.charts(stats.metricsOverTime)
+        this.last = stats.last
+      })
+    }
     api.terminals().then(terminals => {
       this.terminals = terminals
     })
@@ -138,9 +178,48 @@ app.component('appDashboardPage', {
     api.serviceCategories().then(serviceCategories => {
       this.serviceCategories = serviceCategories
     })
+    api.get(`/api/workdays`).then(workdays => {
+      const wds = {}
+      workdays.dates.forEach(x => (wds[x] = true))
+      const getWorkdays = (offset) => {
+        const dates = _.range(offset - 6, offset + 1)
+          .map(x => {
+            const date = moment().startOf('day').add(x, 'days')
+            return {
+              date,
+              formatted: date.format('L'),
+              short: date.format('M/DD'),
+              hasData: wds[date.format('YYYY-MM-DD')]
+            }
+          })
+        return dates
+      }
+
+      this.setOffset = (offset) => {
+        this.offset = offset
+        this.workdays = getWorkdays(offset)
+      }
+
+      this.setDate = (date) => {
+        this.date = date
+        this.now = moment()
+        load(date)
+      }
+
+
+      this.setOffset(0)
+      this.setDate(moment(
+        workdays.dates
+          .map(x => moment(x).valueOf())
+          .sort()
+          .pop() || moment.now()
+      ))
+    })
 
   //   statistics.costPerCarton().then(statistics => {
   //     Object.assign(this, statistics)
   //   })
   }
-})
+})
+
+window.moment = moment

+ 1 - 1
app/services/api.js

@@ -3,7 +3,7 @@ const _ = require('lodash')
 const { dict } = require('@alancnet/material-framework/lib/util')
 
 app.run(function(api) {
-  api.statistics = () => api.get('/api/statistics')
+  api.statistics = (date) => api.get(`/api/statistics/${date.format('YYYY-MM-DD')}`)
 
   let terminals = null
   api.terminals = () => terminals || (terminals = api.get('/api/terminals').then(dict))

+ 3 - 1
lib/controllers/index.js

@@ -3,6 +3,7 @@ const staffMember = require('./staff-member')
 const staffingAgency = require('./staffing-agency')
 const labor = require('./labor')
 const services = require('./services')
+const workdays = require('./workdays')
 const { controllers: C } = require('@alancnet/material-framework/server')
 
 module.exports = Object.assign(C, {
@@ -10,5 +11,6 @@ module.exports = Object.assign(C, {
   staffMember,
   staffingAgency,
   labor,
-  services
+  services,
+  workdays
 })

+ 17 - 3
lib/controllers/statistics.js

@@ -2,12 +2,17 @@ const _ = require('lodash')
 const { Terminal, ServiceCategory } = require('../database')
 const sequelize = require('../database/sequelize')
 const { sanitize } = require('@alancnet/material-framework/lib/util')
+const moment = require('moment-immutable')
 
 const get = async (req, res) => {
   const terminalIds = (await Terminal.findAll())
     .filter(loc => req.claims.TERMINAL_ALL_ACCESS || req.claims[`TERMINAL_${loc.key}_ACCESS`])
     .map(loc => loc.id)
 
+  const date = req.params.date
+  ? moment(req.params.date)
+  : moment((await sequelize.query('select max(date) as date from workdays where laborCost > 0'))[0].date || moment.now())
+
   const [metricsOverTime, metadata] = await sequelize.query(`
     SELECT
       loc.key,
@@ -19,9 +24,15 @@ const get = async (req, res) => {
     JOIN terminals loc on wd.terminalId = loc.id
     LEFT JOIN services svc on svc.workdayId = wd.id
     WHERE loc.id IN(:terminalIds)
+    AND wd.date <= :date
+    AND wd.date >= :startDate
     GROUP BY loc.key, wd.date
   `, {
-    replacements: { terminalIds }
+    replacements: {
+      terminalIds,
+      date: date.format('YYYY-MM-DD'),
+      startDate: date.add(-30, 'days').format('YYYY-MM-DD')
+    }
   })
 
   const metricsSql = (serviceColumn) => `
@@ -51,6 +62,7 @@ const get = async (req, res) => {
     join serviceCategories on laborServiceCategories.serviceCategoryId = serviceCategories.id
 
     where terminals.id in(:terminalIds)
+    and workdays.date = :date
     and serviceCategories.key = :serviceCategory
     group by terminals.key, serviceCategories.key, laborCategories.key
   `
@@ -117,7 +129,7 @@ const get = async (req, res) => {
       join laborServiceCategories on laborServiceCategories.laborCategoryId = laborCategories.id
       join serviceCategories on laborServiceCategories.serviceCategoryId = serviceCategories.id
 
-      where workdays.date = (select max(date) from workdays where laborCost > 0)
+      where workdays.date = :date
       and serviceCategories.key = :serviceCategory
       and terminals.id in(:terminalIds)
 
@@ -127,12 +139,14 @@ const get = async (req, res) => {
   const serviceCategories = (await ServiceCategory.findAll())
   //const opts = {replacements: {serviceCategory: sc.key, terminalIds}}
   const ret = {
+    date,
     serviceCategories: await Promise.all(
       (await ServiceCategory.findAll()).map(async sc => {
         const opts = {
           replacements: {
             terminalIds,
-            serviceCategory: sc.key
+            serviceCategory: sc.key,
+            date: date.format('YYYY-MM-DD')
           }
         }
         const [mpsc] = await sequelize.query(metricsPerServiceCategorySql(sc.serviceColumn), opts)

+ 29 - 0
lib/controllers/workdays.js

@@ -0,0 +1,29 @@
+const { sequelize } = require('../database')
+const moment = require('moment-immutable')
+const { Op } = require('sequelize')
+
+const list = async (req, res) => {
+  const date = req.params.date
+  ? moment(req.params.date)
+  : moment(moment.now())
+
+  const startDate = date.add(-365, 'days')
+  const endDate = date.add(365, 'days')
+
+  const [results, metadata] = await sequelize.query(`
+    select distinct date from workdays where laborCost > 0
+  `)
+
+  const dates = results.map(x => x.date)
+
+  res.status(200).send({
+    startDate: startDate.format('YYYY-MM-DD'),
+    endDate: endDate.format('YYYY-MM-DD'),
+    dates
+  })
+}
+
+
+module.exports = {
+  list
+}

+ 4 - 4
lib/database/seeds/prod.js

@@ -198,10 +198,10 @@ const initializeServiceCategories = async db => {
 }
 
 const seed = async (db) => {
-  await initializeTerminals(db)
-  await initializeRoles(db)
-  await initializeLaborCategories(db)
-  await initializeServiceCategories(db)
+  // await initializeTerminals(db)
+  // //await initializeRoles(db)
+  // await initializeLaborCategories(db)
+  // await initializeServiceCategories(db)
 }
 
 module.exports = Object.assign(seed, {

+ 3 - 1
lib/routes.js

@@ -2,7 +2,7 @@ const C = require('./controllers')
 const { routes:crudRoutes } = require('@alancnet/material-framework/lib/crud')
 
 module.exports = app => {
-  app.get('/api/statistics', C.auth.verify('METRICS_VIEW'), C.statistics.get)
+  app.get('/api/statistics/:date', C.auth.verify('METRICS_VIEW'), C.statistics.get)
   crudRoutes({ app, apiPrefix: '/api/staff-members/:terminal', controller: C.staffMember, camelName: 'staffMember' })
  // crudRoutes({ app, controller: C.staffMember, camelName: 'staffMember' })
   crudRoutes({ app, controller: C.staffingAgency, camelName: 'staffingAgency' })
@@ -17,4 +17,6 @@ module.exports = app => {
   app.get('/api/services/:terminal/:week', C.services.get)
   app.patch('/api/services/:terminal/:week', C.services.patch)
 
+  // Workdays
+  app.get('/api/workdays', C.workdays.list)
 }