diff --git a/atst/domain/reports.py b/atst/domain/reports.py index 36e0bd52..586dda10 100644 --- a/atst/domain/reports.py +++ b/atst/domain/reports.py @@ -4,151 +4,141 @@ from itertools import groupby MONTHLY_SPEND_AARDVARK = { "LC04": { "Integ": { - "10/2018": 284, - "11/2018": 1210, - "12/2018": 1430, - "01/2019": 1366, - "02/2019": 1169, - "03/2019": 991, - "04/2019": 978, - "05/2019": 737, + "02/2018": 284, + "03/2018": 1210, + "04/2018": 1430, + "05/2018": 1366, + "06/2018": 1169, + "07/2018": 991, + "08/2018": 978, + "09/2018": 737, }, "PreProd": { - "10/2018": 812, - "11/2018": 1389, - "12/2018": 1425, - "01/2019": 1306, - "02/2019": 1112, - "03/2019": 936, - "04/2019": 921, - "05/2019": 694, + "02/2018": 812, + "03/2018": 1389, + "04/2018": 1425, + "05/2018": 1306, + "06/2018": 1112, + "07/2018": 936, + "08/2018": 921, + "09/2018": 694, }, "Prod": { - "10/2018": 1742, - "11/2018": 1716, - "12/2018": 1866, - "01/2019": 1809, - "02/2019": 1839, - "03/2019": 1633, - "04/2019": 1654, - "05/2019": 1103, + "02/2018": 1742, + "03/2018": 1716, + "04/2018": 1866, + "05/2018": 1809, + "06/2018": 1839, + "07/2018": 1633, + "08/2018": 1654, + "09/2018": 1103, }, }, "SF18": { "Integ": { - "12/2018": 1498, - "01/2019": 1400, - "02/2019": 1394, - "03/2019": 1171, - "04/2019": 1200, - "05/2019": 963, + "04/2018": 1498, + "05/2018": 1400, + "06/2018": 1394, + "07/2018": 1171, + "08/2018": 1200, + "09/2018": 963, }, "PreProd": { - "12/2018": 1780, - "01/2019": 1667, - "02/2019": 1703, - "03/2019": 1474, - "04/2019": 1441, - "05/2019": 933, + "04/2018": 1780, + "05/2018": 1667, + "06/2018": 1703, + "07/2018": 1474, + "08/2018": 1441, + "09/2018": 933, }, "Prod": { - "12/2018": 1686, - "01/2019": 1779, - "02/2019": 1792, - "03/2019": 1570, - "04/2019": 1539, - "05/2019": 986, + "04/2018": 1686, + "05/2018": 1779, + "06/2018": 1792, + "07/2018": 1570, + "08/2018": 1539, + "09/2018": 986, }, }, "Canton": { "Prod": { - "01/2019": 28699, - "02/2019": 26766, - "03/2019": 22619, - "04/2019": 24090, - "05/2019": 16719, + "05/2018": 28699, + "06/2018": 26766, + "07/2018": 22619, + "08/2018": 24090, + "09/2018": 16719, } }, "BD04": { "Integ": {}, "PreProd": { - "10/2018": 7019, - "11/2018": 3004, - "12/2018": 2691, - "01/2019": 2901, - "02/2019": 3463, - "03/2019": 3314, - "04/2019": 3432, - "05/2019": 723, + "02/2018": 7019, + "03/2018": 3004, + "04/2018": 2691, + "05/2018": 2901, + "06/2018": 3463, + "07/2018": 3314, + "08/2018": 3432, + "09/2018": 723, }, }, "SCV18": {"Dev": {"05/2019": 9797}}, "Crown": { "CR Portal Dev": { - "11/2018": 208, - "12/2018": 457, - "01/2019": 671, - "02/2019": 136, - "03/2019": 1524, - "04/2019": 2077, - "05/2019": 1858, + "03/2018": 208, + "04/2018": 457, + "05/2018": 671, + "06/2018": 136, + "07/2018": 1524, + "08/2018": 2077, + "09/2018": 1858, }, "CR Staging": { - "11/2018": 208, - "12/2018": 457, - "01/2019": 671, - "02/2019": 136, - "03/2019": 1524, - "04/2019": 2077, - "05/2019": 1858, + "03/2018": 208, + "04/2018": 457, + "05/2018": 671, + "06/2018": 136, + "07/2018": 1524, + "08/2018": 2077, + "09/2018": 1858, }, - "CR Portal Test 1": {"03/2019": 806, "04/2019": 1966, "05/2019": 2597}, - "Jewels Prod": {"03/2019": 806, "04/2019": 1966, "05/2019": 2597}, + "CR Portal Test 1": {"07/2018": 806, "08/2018": 1966, "09/2018": 2597}, + "Jewels Prod": {"07/2018": 806, "08/2018": 1966, "09/2018": 2597}, "Jewels Dev": { - "11/2018": 145, - "12/2018": 719, - "01/2019": 1243, - "02/2019": 2214, - "03/2019": 2959, - "04/2019": 4151, - "05/2019": 4260, + "03/2018": 145, + "04/2018": 719, + "05/2018": 1243, + "06/2018": 2214, + "07/2018": 2959, + "08/2018": 4151, + "09/2018": 4260, }, }, } CUMULATIVE_BUDGET_AARDVARK = { - "10/2018": {"spend": 9857, "cumulative": 9857}, - "11/2018": {"spend": 7881, "cumulative": 17738}, - "12/2018": {"spend": 14010, "cumulative": 31748}, - "01/2019": {"spend": 43510, "cumulative": 75259}, - "02/2019": {"spend": 41725, "cumulative": 116984}, - "03/2019": {"spend": 41328, "cumulative": 158312}, - "04/2019": {"spend": 47491, "cumulative": 205803}, - "05/2019": {"spend": 45826, "cumulative": 251629}, - "06/2019": {"projected": 296511}, - "07/2019": {"projected": 341393}, - "08/2019": {"projected": 386274}, - "09/2019": {"projected": 431156}, + "02/2018": {"spend": 9857, "cumulative": 9857}, + "03/2018": {"spend": 7881, "cumulative": 17738}, + "04/2018": {"spend": 14010, "cumulative": 31748}, + "05/2018": {"spend": 43510, "cumulative": 75259}, + "06/2018": {"spend": 41725, "cumulative": 116984}, + "07/2018": {"spend": 41328, "cumulative": 158312}, + "08/2018": {"spend": 47491, "cumulative": 205803}, + "09/2018": {"spend": 45826, "cumulative": 251629}, } MONTHLY_SPEND_BELUGA = { "NP02": { - "Integ": {"02/2019": 284, "03/2019": 1210}, - "PreProd": {"02/2019": 812, "03/2019": 1389}, - "Prod": {"02/2019": 3742, "03/2019": 4716}, + "Integ": {"08/2018": 284, "09/2018": 1210}, + "PreProd": {"08/2018": 812, "09/2018": 1389}, + "Prod": {"08/2018": 3742, "09/2018": 4716}, }, - "FM": {"Integ": {"03/2019": 1498}, "Prod": {"03/2019": 5686}}, + "FM": {"Integ": {"08/2018": 1498}, "Prod": {"09/2018": 5686}}, } CUMULATIVE_BUDGET_BELUGA = { - "02/2019": {"spend": 4838, "cumulative": 4838}, - "03/2019": {"spend": 14500, "cumulative": 19338}, - "04/2019": {"projected": 29007}, - "05/2019": {"projected": 38676}, - "06/2019": {"projected": 48345}, - "07/2019": {"projected": 58014}, - "08/2019": {"projected": 67683}, - "09/2019": {"projected": 77352}, + "08/2018": {"spend": 4838, "cumulative": 4838}, + "09/2018": {"spend": 14500, "cumulative": 19338}, } diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 4ee51150..365f2836 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -106,6 +106,11 @@ def workspace_reports(workspace_id): prev_month = current_month - timedelta(days=28) two_months_ago = prev_month - timedelta(days=28) + # lets just say it expires on Christmas... ho ho ho + expiration_date = date(2018, 12, 25) + remaining_difference = expiration_date - today + remaining_days = remaining_difference.days + return render_template( "workspaces/reports/index.html", cumulative_budget=Reports.cumulative_budget(alternate_reports), @@ -114,6 +119,8 @@ def workspace_reports(workspace_id): current_month=current_month, prev_month=prev_month, two_months_ago=two_months_ago, + expiration_date=expiration_date, + remaining_days=remaining_days, ) diff --git a/js/components/charts/budget_chart.js b/js/components/charts/budget_chart.js index 68f071fe..d4ea7a96 100644 --- a/js/components/charts/budget_chart.js +++ b/js/components/charts/budget_chart.js @@ -1,14 +1,15 @@ -import { format } from 'date-fns' +import { format, isWithinRange, addMonths, isSameMonth, getMonth } from 'date-fns' import { abbreviateDollars, formatDollars } from '../../lib/dollars' const TOP_OFFSET = 20 -const BOTTOM_OFFSET = 60 +const BOTTOM_OFFSET = 70 const CHART_HEIGHT = 360 export default { name: 'budget-chart', props: { currentMonth: String, + expirationDate: String, months: Object, budget: String }, @@ -46,18 +47,15 @@ export default { let lastSpendPoint = '' for (let i = 0; i < this.numMonths; i++) { - const { metrics, budget } = this.displayedMonths[i] + const { metrics, budget, rollingAverage, cumulativeTotal } = this.displayedMonths[i] const blockWidth = (this.width / this.numMonths) const blockX = blockWidth * i - const spend = budget - ? budget.spend || lastSpend - : 0 - const cumulative = budget - ? budget.cumulative || budget.projected - : 0 + const spend = budget && budget.spend + ? budget.spend + : rollingAverage const barHeight = spend / this.heightScale lastSpend = spend - const cumulativeY = this.height - (cumulative / this.heightScale) - BOTTOM_OFFSET + const cumulativeY = this.height - (cumulativeTotal / this.heightScale) - BOTTOM_OFFSET const cumulativeX = blockX + blockWidth/2 const cumulativePoint = `${cumulativeX} ${cumulativeY}` @@ -77,9 +75,7 @@ export default { this.spendPath += this.spendPath === '' ? 'M' : ' L' this.spendPath += cumulativePoint lastSpendPoint = cumulativePoint - } - - if (budget && budget.projected) { + } else { this.projectedPath += this.projectedPath === '' ? `M${lastSpendPoint} L` : ' L' this.projectedPath += cumulativePoint } @@ -88,32 +84,76 @@ export default { _setDisplayedMonths: function () { const [month, year] = this.currentMonth.split('/') + const [expYear, expMonth, expDate] = this.expirationDate.split('-') // assumes format 'YYYY-MM-DD' const monthsRange = [] - const monthsBack = this.focusedMonthPosition + const monthsBack = this.focusedMonthPosition + 1 const monthsForward = this.numMonths - this.focusedMonthPosition - 1 - const start = new Date(year, month - 1 - monthsBack) - let previousAmount = 0 + // currently focused date + const current = new Date(year, month) + + // starting date of the chart + const start = addMonths(current, -monthsBack) + + // ending date of the chart + const end = addMonths(start, this.numMonths + 1) + + // expiration date + const expires = new Date(expYear, expMonth-1, expDate) + + // is the expiration date within the displayed date range? + const expirationWithinRange = isWithinRange(expires, start, end) + + let rollingAverage = 0 + let cumulativeTotal = 0 for (let i = 0; i < this.numMonths; i++) { - const date = new Date(start.getFullYear(), start.getMonth() + i) + const date = addMonths(start, i) + const dateMinusOne = addMonths(date, -1) + const dateMinusTwo = addMonths(date, -2) + const dateMinusThree = addMonths(date, -3) + const index = format(date, 'MM/YYYY') + const indexMinusOne = format(dateMinusOne, 'MM/YYYY') + const indexMinusTwo = format(dateMinusTwo, 'MM/YYYY') + const indexMinusThree = format(dateMinusThree, 'MM/YYYY') + const budget = this.months[index] || null - const spendAmount = budget ? budget.spend || previousAmount : 0 - const cumulativeAmount = budget ? budget.cumulative || budget.projected : 0 - previousAmount = spendAmount + const spendAmount = budget ? budget.spend : rollingAverage + const spendMinusOne = this.months[indexMinusOne] ? this.months[indexMinusOne].spend : rollingAverage + const spendMinusTwo = this.months[indexMinusTwo] ? this.months[indexMinusTwo].spend : rollingAverage + const spendMinusThree = this.months[indexMinusThree] ? this.months[indexMinusThree].spend : rollingAverage + + const isExpirationMonth = isSameMonth(date, expires) + + if (budget && budget.cumulative) { + cumulativeTotal = budget.cumulative + } else { + cumulativeTotal += spendAmount + } + + rollingAverage = ( + spendAmount + + spendMinusOne + + spendMinusTwo + + spendMinusThree + ) / 4 monthsRange.push({ budget, + rollingAverage, + cumulativeTotal, + isExpirationMonth, spendAmount: formatDollars(spendAmount), abbreviatedSpend: abbreviateDollars(spendAmount), - cumulativeAmount: formatDollars(cumulativeAmount), - abbreviatedCumulative: abbreviateDollars(cumulativeAmount), + cumulativeAmount: formatDollars(cumulativeTotal), + abbreviatedCumulative: abbreviateDollars(cumulativeTotal), date: { monthIndex: format(date, 'M'), month: format(date, 'MMM'), year: format(date,'YYYY') }, + showYear: isExpirationMonth || (i === 0) || getMonth(date) === 0, isHighlighted: this.currentMonth === index, metrics: { blockWidth: 0, diff --git a/styles/components/_budget_chart.scss b/styles/components/_budget_chart.scss index 92c22c39..b6b8a0e3 100644 --- a/styles/components/_budget_chart.scss +++ b/styles/components/_budget_chart.scss @@ -60,21 +60,39 @@ } .budget-chart__block { - fill: $color-white; + fill: transparent; cursor: pointer; &--highlighted { - fill: $color-aqua-lightest; + fill: rgba($color-aqua, .15); + } + + &--is-expiration { + border-left: 2px dotted $color-gray; } &:hover { - fill: $color-aqua-lightest; + fill: rgba($color-aqua, .15); } } svg { display: block; + .filter__text-background { + feFlood { + flood-color: $color-white; + flood-opacity: 1; + } + + &--highlighted { + feFlood { + flood-color: $color-aqua-lightest; + flood-opacity: 1; + } + } + } + a { text-decoration: none; &:focus { @@ -82,6 +100,15 @@ stroke: $color-gray-light; stroke-dasharray: 2px; } + + &:hover { + .filter__text-background { + feFlood { + flood-color: $color-aqua-lightest; + flood-opacity: 1; + } + } + } } } @@ -96,6 +123,12 @@ } } + .budget-chart__expiration-line { + stroke-width: 2px; + stroke: $color-gray-light; + stroke-dasharray: 4px; + } + .budget-chart__cumulative__dot { fill: $color-gold; } @@ -122,6 +155,7 @@ .budget-chart__label { @include small-label; fill: $color-gray; + pointer-events: none; &--strong { fill: $color-black; diff --git a/templates/workspaces/reports/index.html b/templates/workspaces/reports/index.html index 4920b2fd..172d5e07 100644 --- a/templates/workspaces/reports/index.html +++ b/templates/workspaces/reports/index.html @@ -63,12 +63,17 @@
Expires
-
November 1, 2019
+
+ + +
Remaining
-
200 days
+
{{ remaining_days }} days
@@ -97,7 +102,12 @@ {% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %} {% set reports_url = url_for("workspaces.workspace_reports", workspace_id=workspace.id) %} - +

Cumulative Budget

@@ -127,18 +137,54 @@
+ + + + + + + + + {# spend/projected budget path lines #} + + + + {# max budget line #} + + + {# make this clickable to focus on that month #} + + + + + + + +  |