Browse Source

the start of reporting and some cleanup

develop
Rob Colbert 12 months ago
parent
commit
ee2ecd7a22
  1. 1
      app/controllers/client.js
  2. 3
      app/controllers/home.js
  3. 40
      app/controllers/report.js
  4. 60
      app/services/report.js
  5. 6
      app/services/task.js
  6. 3
      app/views/components/navbar.pug
  7. 13
      app/views/home.pug
  8. 16
      app/views/report/dashboard.pug

1
app/controllers/client.js

@ -15,7 +15,6 @@ export default class ClientController extends SiteController {
constructor (dtp) { constructor (dtp) {
super(dtp, ClientController.slug); super(dtp, ClientController.slug);
this.dtp = dtp;
} }
async start ( ) { async start ( ) {

3
app/controllers/home.js

@ -28,7 +28,7 @@ export default class HomeController extends SiteController {
} }
async getHome (req, res, next) { async getHome (req, res, next) {
const { client: clientService, task: taskService } = this.dtp.services; const { client: clientService, report: reportService, task: taskService } = this.dtp.services;
try { try {
if (!req.user) { if (!req.user) {
return res.redirect('/welcome'); return res.redirect('/welcome');
@ -41,6 +41,7 @@ export default class HomeController extends SiteController {
res.locals.projects = await clientService.getProjectsForUser(req.user); res.locals.projects = await clientService.getProjectsForUser(req.user);
res.locals.taskGrid = await taskService.getTaskGridForUser(req.user); res.locals.taskGrid = await taskService.getTaskGridForUser(req.user);
res.locals.weeklyEarnings = await reportService.getWeeklyEarnings(req.user);
res.render('home'); res.render('home');
} catch (error) { } catch (error) {

40
app/controllers/report.js

@ -0,0 +1,40 @@
// report.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import express from 'express';
import { SiteController } from '../../lib/site-lib.js';
export default class ReportController extends SiteController {
static get name ( ) { return 'ReportController'; }
static get slug ( ) { return 'report'; }
constructor (dtp) {
super(dtp, ReportController.slug);
}
async start ( ) {
// const { limiter: limiterService } = dtp.services;
// const limiterConfig = limiterService.config.report;
const router = express.Router();
this.dtp.app.use('/report', router);
router.get('/', this.getDashboard.bind(this));
}
async getDashboard (req, res, next) {
const { report: reportService } = this.dtp.services;
try {
res.locals.weeklyEarnings = await reportService.getWeeklyEarnings(req.user);
res.render('report/dashboard');
} catch (error) {
this.log.error('failed to present report dashboard', { error });
return next(error);
}
}
}

60
app/services/report.js

@ -0,0 +1,60 @@
// report.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import mongoose from 'mongoose';
const TaskSession = mongoose.model('TaskSession');
import dayjs from 'dayjs';
import { SiteService } from '../../lib/site-lib.js';
export default class ReportService extends SiteService {
static get name ( ) { return 'ReportService'; }
static get slug () { return 'report'; }
constructor (dtp) {
super(dtp, ReportService);
}
async getWeeklyEarnings (user) {
const NOW = new Date();
const dateStart = this.startOfWeek(NOW);
const dateEnd = dayjs(dateStart).add(1, 'week').toDate();
this.log.debug('computing weekly earnings', { dateStart, dateEnd });
/*
* I'm sure there's some beautiful way to do this using aggregation but I
* don't care at all (and won't) until aggregation becomes a usable API.
*/
const response = { sessionCount: 0, duration: 0, billable: 0 };
await TaskSession
.find({
user: user._id,
$and: [
{ created: { $gte: dateStart } },
{ finished: { $lt: dateEnd } },
],
})
.cursor()
.eachAsync(async (session) => {
response.sessionCount += 1;
response.duration += session.duration;
response.billable += session.hourlyRate * (session.duration / 60 / 60);
});
return response;
}
startOfWeek (date) {
date = date || new Date();
date.setHours(0,0,0,0);
var diff = date.getDate() - date.getDay() + (date.getDay() === 0 ? -6 : 1);
return new Date(date.setDate(diff));
}
}

6
app/services/task.js

@ -285,10 +285,4 @@ export default class TaskService extends SiteService {
await this.closeTaskSession(session); await this.closeTaskSession(session);
}); });
} }
startOfWeek (date) {
date = date || new Date();
var diff = date.getDate() - date.getDay() + (date.getDay() === 0 ? -6 : 1);
return new Date(date.setDate(diff));
}
} }

3
app/views/components/navbar.pug

@ -13,6 +13,9 @@ nav(style="background: #000000;").uk-navbar-container.uk-light
li li
a(href="/client", aria-label="Manage your clients").uk-navbar-item a(href="/client", aria-label="Manage your clients").uk-navbar-item
.uk-text-bold.no-select Clients .uk-text-bold.no-select Clients
li
a(href="/report", aria-label="View your work reports").uk-navbar-item
.uk-text-bold.no-select Reports
.uk-navbar-right .uk-navbar-right
if !user if !user

13
app/views/home.pug

@ -3,6 +3,19 @@ block view-content
include task/components/grid include task/components/grid
section.uk-section.uk-section-secondary.uk-section-small
.uk-container
div(uk-grid).uk-flex-between
.uk-width-auto
div sessions
.uk-text-large.uk-text-bold= formatCount(weeklyEarnings.sessionCount)
.uk-width-auto
div time worked
.uk-text-large.uk-text-bold= numeral(weeklyEarnings.duration).format('0:00:00')
.uk-width-auto
div billable
.uk-text-large.uk-text-bold= numeral(weeklyEarnings.billable).format('$0,0.00')
section.uk-section.uk-section-default section.uk-section.uk-section-default
.uk-container .uk-container
+renderTaskGrid( +renderTaskGrid(

16
app/views/report/dashboard.pug

@ -0,0 +1,16 @@
extends ../layout/main
block view-content
section.uk-section.uk-section-default.uk-section
.uk-container
div(uk-grid).uk-flex-between
.uk-width-auto
.uk-text-bold.uk-text-small Sessions
div= formatCount(weeklyEarnings.sessionCount)
.uk-width-auto
.uk-text-bold.uk-text-small Hours
div= numeral(weeklyEarnings.duration).format('0:00:00')
.uk-width-auto
.uk-text-bold.uk-text-small Billable
div= numeral(weeklyEarnings.billable).format('$0,0.00')
Loading…
Cancel
Save