8 changed files with 134 additions and 8 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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)); |
|||
} |
|||
} |
@ -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…
Reference in new issue