Merge pull request #262 from dod-ccpo/ui/cumulative-budget-chart
Ui/cumulative budget chart
This commit is contained in:
commit
776796beee
@ -196,3 +196,11 @@ class Reports:
|
|||||||
"projects": project_totals,
|
"projects": project_totals,
|
||||||
"workspace": workspace_totals,
|
"workspace": workspace_totals,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cumulative_budget(cls, alternate):
|
||||||
|
return {
|
||||||
|
"months": CUMULATIVE_BUDGET_BELUGA
|
||||||
|
if alternate
|
||||||
|
else CUMULATIVE_BUDGET_AARDVARK
|
||||||
|
}
|
||||||
|
@ -87,6 +87,7 @@ def workspace_reports(workspace_id):
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"workspaces/reports/index.html",
|
"workspaces/reports/index.html",
|
||||||
|
cumulative_budget=Reports.cumulative_budget(alternate_reports),
|
||||||
workspace_totals=Reports.workspace_totals(alternate_reports),
|
workspace_totals=Reports.workspace_totals(alternate_reports),
|
||||||
monthly_totals=Reports.monthly_totals(alternate_reports),
|
monthly_totals=Reports.monthly_totals(alternate_reports),
|
||||||
current_month=current_month,
|
current_month=current_month,
|
||||||
|
135
js/components/charts/budget_chart.js
Normal file
135
js/components/charts/budget_chart.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { format } from 'date-fns'
|
||||||
|
import { abbreviateDollars, formatDollars } from '../../lib/dollars'
|
||||||
|
|
||||||
|
const TOP_OFFSET = 20
|
||||||
|
const BOTTOM_OFFSET = 60
|
||||||
|
const CHART_HEIGHT = 360
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'budget-chart',
|
||||||
|
props: {
|
||||||
|
currentMonth: String,
|
||||||
|
months: Object,
|
||||||
|
budget: String
|
||||||
|
},
|
||||||
|
|
||||||
|
data: function () {
|
||||||
|
const heightScale = this.budget / (CHART_HEIGHT - TOP_OFFSET - BOTTOM_OFFSET)
|
||||||
|
return {
|
||||||
|
numMonths: 10,
|
||||||
|
focusedMonthPosition: 4,
|
||||||
|
height: CHART_HEIGHT,
|
||||||
|
heightScale,
|
||||||
|
budgetHeight: CHART_HEIGHT - BOTTOM_OFFSET - (this.budget / heightScale),
|
||||||
|
baseHeight: CHART_HEIGHT - BOTTOM_OFFSET,
|
||||||
|
width: 0,
|
||||||
|
displayedMonths: [],
|
||||||
|
spendPath: '',
|
||||||
|
projectedPath: '',
|
||||||
|
displayBudget: formatDollars(parseFloat(this.budget))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function () {
|
||||||
|
this._setDisplayedMonths()
|
||||||
|
this._setMetrics()
|
||||||
|
addEventListener('resize', this._setMetrics)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
_setMetrics: function () {
|
||||||
|
this.width = this.$refs.panel.clientWidth
|
||||||
|
this.spendPath = ''
|
||||||
|
this.projectedPath = ''
|
||||||
|
|
||||||
|
let lastSpend = 0
|
||||||
|
let lastSpendPoint = ''
|
||||||
|
|
||||||
|
for (let i = 0; i < this.numMonths; i++) {
|
||||||
|
const { metrics, budget } = 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 barHeight = spend / this.heightScale
|
||||||
|
lastSpend = spend
|
||||||
|
const cumulativeY = this.height - (cumulative / this.heightScale) - BOTTOM_OFFSET
|
||||||
|
const cumulativeX = blockX + blockWidth/2
|
||||||
|
const cumulativePoint = `${cumulativeX} ${cumulativeY}`
|
||||||
|
|
||||||
|
this.displayedMonths[i].metrics = Object.assign(metrics, {
|
||||||
|
blockWidth,
|
||||||
|
blockX,
|
||||||
|
barHeight,
|
||||||
|
barWidth: 30,
|
||||||
|
barX: blockX + (blockWidth/2 - 15),
|
||||||
|
barY: this.height - barHeight - BOTTOM_OFFSET,
|
||||||
|
cumulativeR: 2.5,
|
||||||
|
cumulativeY,
|
||||||
|
cumulativeX
|
||||||
|
})
|
||||||
|
|
||||||
|
if (budget && budget.spend) {
|
||||||
|
this.spendPath += this.spendPath === '' ? 'M' : ' L'
|
||||||
|
this.spendPath += cumulativePoint
|
||||||
|
lastSpendPoint = cumulativePoint
|
||||||
|
}
|
||||||
|
|
||||||
|
if (budget && budget.projected) {
|
||||||
|
this.projectedPath += this.projectedPath === '' ? `M${lastSpendPoint} L` : ' L'
|
||||||
|
this.projectedPath += cumulativePoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setDisplayedMonths: function () {
|
||||||
|
const [month, year] = this.currentMonth.split('/')
|
||||||
|
const monthsRange = []
|
||||||
|
const monthsBack = this.focusedMonthPosition
|
||||||
|
const monthsForward = this.numMonths - this.focusedMonthPosition - 1
|
||||||
|
const start = new Date(year, month - 1 - monthsBack)
|
||||||
|
|
||||||
|
let previousAmount = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < this.numMonths; i++) {
|
||||||
|
const date = new Date(start.getFullYear(), start.getMonth() + i)
|
||||||
|
const index = format(date, '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
|
||||||
|
|
||||||
|
monthsRange.push({
|
||||||
|
budget,
|
||||||
|
spendAmount: formatDollars(spendAmount),
|
||||||
|
abbreviatedSpend: abbreviateDollars(spendAmount),
|
||||||
|
cumulativeAmount: formatDollars(cumulativeAmount),
|
||||||
|
abbreviatedCumulative: abbreviateDollars(cumulativeAmount),
|
||||||
|
date: {
|
||||||
|
monthIndex: format(date, 'M'),
|
||||||
|
month: format(date, 'MMM'),
|
||||||
|
year: format(date,'YYYY')
|
||||||
|
},
|
||||||
|
isHighlighted: this.currentMonth === index,
|
||||||
|
metrics: {
|
||||||
|
blockWidth: 0,
|
||||||
|
blockX: 0,
|
||||||
|
barHeight: 0,
|
||||||
|
barWidth: 0,
|
||||||
|
barX: 0,
|
||||||
|
barY: 0,
|
||||||
|
cumulativeY: 0,
|
||||||
|
cumulativeX: 0,
|
||||||
|
cumulativeR: 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
this.displayedMonths = monthsRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'svg-innerhtml'
|
||||||
import 'babel-polyfill'
|
import 'babel-polyfill'
|
||||||
|
|
||||||
import classes from '../styles/atat.scss'
|
import classes from '../styles/atat.scss'
|
||||||
@ -14,6 +15,7 @@ import toggler from './components/toggler'
|
|||||||
import NewProject from './components/forms/new_project'
|
import NewProject from './components/forms/new_project'
|
||||||
import Modal from './mixins/modal'
|
import Modal from './mixins/modal'
|
||||||
import selector from './components/selector'
|
import selector from './components/selector'
|
||||||
|
import BudgetChart from './components/charts/budget_chart'
|
||||||
|
|
||||||
Vue.use(VTooltip)
|
Vue.use(VTooltip)
|
||||||
|
|
||||||
@ -30,7 +32,8 @@ const app = new Vue({
|
|||||||
poc,
|
poc,
|
||||||
financial,
|
financial,
|
||||||
NewProject,
|
NewProject,
|
||||||
selector
|
selector,
|
||||||
|
BudgetChart
|
||||||
},
|
},
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
const modalOpen = document.querySelector("#modalOpen")
|
const modalOpen = document.querySelector("#modalOpen")
|
||||||
|
12
js/lib/dollars.js
Normal file
12
js/lib/dollars.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const formatDollars = value => `$${value.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')}`
|
||||||
|
|
||||||
|
export const abbreviateDollars = (value, decimals = 1) => {
|
||||||
|
if (value === null) { return null } // terminate early
|
||||||
|
if (value === 0) { return '0' } // terminate early
|
||||||
|
var b = (value).toPrecision(2).split("e"), // get power
|
||||||
|
k = b.length === 1 ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3), // floor at decimals, ceiling at trillions
|
||||||
|
c = k < 1 ? value.toFixed(0 + decimals) : (value / Math.pow(10, k * 3) ).toFixed(decimals), // divide by power
|
||||||
|
d = c < 0 ? c : Math.abs(c), // enforce -0 is 0
|
||||||
|
e = d + ['', 'k', 'M', 'B', 'T'][k]; // append power
|
||||||
|
return e;
|
||||||
|
}
|
@ -13,8 +13,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^9.1.3",
|
"autoprefixer": "^9.1.3",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"date-fns": "^1.29.0",
|
||||||
"npm": "^6.0.1",
|
"npm": "^6.0.1",
|
||||||
"parcel": "^1.9.7",
|
"parcel": "^1.9.7",
|
||||||
|
"svg-innerhtml": "^1.1.0",
|
||||||
"text-mask-addons": "^3.8.0",
|
"text-mask-addons": "^3.8.0",
|
||||||
"uswds": "^1.6.3",
|
"uswds": "^1.6.3",
|
||||||
"v-tooltip": "^2.0.0-rc.33",
|
"v-tooltip": "^2.0.0-rc.33",
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
@import 'components/search_bar';
|
@import 'components/search_bar';
|
||||||
@import 'components/forms';
|
@import 'components/forms';
|
||||||
@import 'components/selector';
|
@import 'components/selector';
|
||||||
|
@import 'components/budget_chart';
|
||||||
|
|
||||||
@import 'sections/login';
|
@import 'sections/login';
|
||||||
@import 'sections/request_approval';
|
@import 'sections/request_approval';
|
||||||
|
130
styles/components/_budget_chart.scss
Normal file
130
styles/components/_budget_chart.scss
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
.budget-chart {
|
||||||
|
.budget-chart__header {
|
||||||
|
border-bottom: 1px solid $color-gray-light;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.budget-chart__legend {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
dl {
|
||||||
|
margin: 0 0 0 ($gap * 2);
|
||||||
|
|
||||||
|
> div {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
dt {
|
||||||
|
@include small-label;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__legend__dot {
|
||||||
|
width: $gap;
|
||||||
|
height: $gap;
|
||||||
|
border-radius: $gap / 2;
|
||||||
|
margin: 0 $gap;
|
||||||
|
|
||||||
|
&.accumulated {
|
||||||
|
background-color: $color-gold;
|
||||||
|
}
|
||||||
|
&.monthly {
|
||||||
|
background-color: $color-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__legend__line {
|
||||||
|
height: 2px;
|
||||||
|
width: $gap * 3;
|
||||||
|
border-top-width: 2px;
|
||||||
|
border-top-style: dashed;
|
||||||
|
margin: $gap;
|
||||||
|
|
||||||
|
&.spend {
|
||||||
|
border-color: $color-blue;
|
||||||
|
}
|
||||||
|
&.accumulated {
|
||||||
|
border-color: $color-gold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__block {
|
||||||
|
fill: $color-white;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&--highlighted {
|
||||||
|
fill: $color-aqua-lightest;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: $color-aqua-lightest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
stroke: $color-gray-light;
|
||||||
|
stroke-dasharray: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__bar {
|
||||||
|
fill: $color-blue;
|
||||||
|
|
||||||
|
&--projected {
|
||||||
|
fill: transparent;
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke: $color-blue;
|
||||||
|
stroke-dasharray: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__cumulative__dot {
|
||||||
|
fill: $color-gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__projected-path {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke: $color-gold;
|
||||||
|
stroke-dasharray: 4px;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__spend-path {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke: $color-gold;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__budget-line {
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke: $color-gray-light;
|
||||||
|
stroke-dasharray: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.budget-chart__label {
|
||||||
|
@include small-label;
|
||||||
|
fill: $color-gray;
|
||||||
|
|
||||||
|
&--strong {
|
||||||
|
fill: $color-black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,5 +42,6 @@
|
|||||||
&.col--grow {
|
&.col--grow {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
overflow: auto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,13 +62,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.panel__heading {
|
.panel__heading {
|
||||||
margin: $gap * 2;
|
padding: $gap * 2;
|
||||||
@include media($medium-screen) {
|
@include media($medium-screen) {
|
||||||
margin: $gap * 4;
|
padding: $gap * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--tight {
|
&--tight {
|
||||||
margin: $gap*2;
|
padding: $gap*2;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
@ -68,3 +68,9 @@ dl {
|
|||||||
margin-bottom: $gap * 2;
|
margin-bottom: $gap * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin small-label {
|
||||||
|
font-size: $h6-font-size;
|
||||||
|
font-weight: $font-bold;
|
||||||
|
color: $color-black;
|
||||||
|
}
|
||||||
|
@ -134,16 +134,16 @@
|
|||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
<div is='toggler' default-visible class='block-list project-list-item'>
|
<div is='toggler' default-visible class='block-list project-list-item'>
|
||||||
<template slot-scope='{ isVisible, toggle }'>
|
<template slot-scope='props'>
|
||||||
<header class='block-list__header'>
|
<header class='block-list__header'>
|
||||||
<button v-on:click='toggle' class='icon-link icon-link--large icon-link--default spend-table__project__toggler'>
|
<button type='button' v-on:click='props.toggle' class='icon-link icon-link--large icon-link--default spend-table__project__toggler'>
|
||||||
<template v-if='isVisible'>{{ Icon('caret_down') }}</template>
|
<template v-if='props.isVisible'>{{ Icon('caret_down') }}</template>
|
||||||
<template v-else>{{ Icon('caret_right') }}</template>
|
<template v-else>{{ Icon('caret_right') }}</template>
|
||||||
<h3 class="block-list__title">Code.mil</h3>
|
<h3 class="block-list__title">Code.mil</h3>
|
||||||
</button>
|
</button>
|
||||||
<span><a href="#" class="icon-link icon-link--danger">revoke all access</a></span>
|
<span><a href="#" class="icon-link icon-link--danger">revoke all access</a></span>
|
||||||
</header>
|
</header>
|
||||||
<ul v-show='isVisible'>
|
<ul v-show='props.isVisible'>
|
||||||
<li class='block-list__item project-list-item__environment'>
|
<li class='block-list__item project-list-item__environment'>
|
||||||
<span class='project-list-item__environment'>
|
<span class='project-list-item__environment'>
|
||||||
Development
|
Development
|
||||||
@ -173,16 +173,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div is="toggler" class='block-list project-list-item'>
|
<div is="toggler" class='block-list project-list-item'>
|
||||||
<template slot-scope='{ isVisible, toggle }'>
|
<template slot-scope='props'>
|
||||||
<header class='block-list__header'>
|
<header class='block-list__header'>
|
||||||
<button v-on:click='toggle' class='icon-link icon-link--large icon-link--default spend-table__project__toggler'>
|
<button type='button' v-on:click='props.toggle' class='icon-link icon-link--large icon-link--default spend-table__project__toggler'>
|
||||||
<template v-if='isVisible'>{{ Icon('caret_down') }}</template>
|
<template v-if='props.isVisible'>{{ Icon('caret_down') }}</template>
|
||||||
<template v-else>{{ Icon('caret_right') }}</template>
|
<template v-else>{{ Icon('caret_right') }}</template>
|
||||||
<h3 class="block-list__title">Digital Dojo</h3>
|
<h3 class="block-list__title">Digital Dojo</h3>
|
||||||
</button>
|
</button>
|
||||||
<span class="label">no access</span>
|
<span class="label">no access</span>
|
||||||
</header>
|
</header>
|
||||||
<ul v-show='isVisible'>
|
<ul v-show='props.isVisible'>
|
||||||
<li class='block-list__item project-list-item__environment'>
|
<li class='block-list__item project-list-item__environment'>
|
||||||
<span class='project-list-item__environment'>
|
<span class='project-list-item__environment'>
|
||||||
Development
|
Development
|
||||||
|
@ -93,6 +93,133 @@
|
|||||||
{% set current_month_index = current_month.strftime('%m/%Y') %}
|
{% set current_month_index = current_month.strftime('%m/%Y') %}
|
||||||
{% set prev_month_index = prev_month.strftime('%m/%Y') %}
|
{% set prev_month_index = prev_month.strftime('%m/%Y') %}
|
||||||
{% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}
|
{% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}
|
||||||
|
{% set reports_url = url_for("workspaces.workspace_reports", workspace_id=workspace.id) %}
|
||||||
|
|
||||||
|
<budget-chart budget={{ budget }} current-month='{{ current_month_index }}' v-bind:months='{{ cumulative_budget.months | tojson }}' inline-template>
|
||||||
|
<div class='budget-chart panel' ref='panel'>
|
||||||
|
<header class='budget-chart__header panel__heading panel__heading--tight'>
|
||||||
|
<h2 class='h3'>Cumulative Budget</h2>
|
||||||
|
|
||||||
|
<div class='budget-chart__legend'>
|
||||||
|
<dl class='budget-chart__legend__spend'>
|
||||||
|
<div>
|
||||||
|
<dt>Monthly Spend</dt>
|
||||||
|
<dd class='budget-chart__legend__dot monthly'><span class='usa-sr-only'>Monthly spend visual key</span></dd>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<dt>Accumulated Spend</dt>
|
||||||
|
<dd class='budget-chart__legend__dot accumulated'><span class='usa-sr-only'>Accumulated spend visual key</span></dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
<dl class='budget-chart__legend__projected'>
|
||||||
|
<div>
|
||||||
|
<dt>Projected</dt>
|
||||||
|
<dd>
|
||||||
|
<div class='budget-chart__legend__line spend'><span class='usa-sr-only'>Projected monthly spend visual key</span></div>
|
||||||
|
<div class='budget-chart__legend__line accumulated'><span class='usa-sr-only'>Projected accumulated spend visual key</span></div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<svg v-bind:height='height' v-bind:width='width'>
|
||||||
|
<g v-for='month in displayedMonths' >
|
||||||
|
{# make this clickable to focus on that month #}
|
||||||
|
<a v-bind:href='"{{ reports_url }}?month=" + month.date.monthIndex + "&year=" + month.date.year'>
|
||||||
|
<title>
|
||||||
|
<span v-html='month.date.month + " " + month.date.year'></span> | <!--
|
||||||
|
--><template v-if='month.budget'><!--
|
||||||
|
--><template v-if='month.budget.spend'>Spend:</template><!--
|
||||||
|
--><template v-if='month.budget.projected'>Projected Spend:</template><!--
|
||||||
|
--><span v-html='month.spendAmount'></span><!--
|
||||||
|
--> | <!--
|
||||||
|
--><template v-if='month.budget.cumulative'>Total:</template><!--
|
||||||
|
--><template v-if='month.budget.projected'>Projected Total:</template><!--
|
||||||
|
--><span v-html='month.cumulativeAmount'></span><!--
|
||||||
|
--></template><!--
|
||||||
|
|
||||||
|
--><template v-else>No spend for this month</template>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
{# container block #}
|
||||||
|
<rect
|
||||||
|
class='budget-chart__block'
|
||||||
|
v-bind:class='{ "budget-chart__block--highlighted": month.isHighlighted }'
|
||||||
|
v-bind:width='month.metrics.blockWidth'
|
||||||
|
v-bind:x='month.metrics.blockX'
|
||||||
|
v-bind:height='height'></rect>
|
||||||
|
|
||||||
|
{# budget bar #}
|
||||||
|
<rect
|
||||||
|
v-if='month.budget'
|
||||||
|
class='budget-chart__bar'
|
||||||
|
v-bind:class='{ "budget-chart__bar--projected": month.budget.projected }'
|
||||||
|
v-bind:width='month.metrics.barWidth'
|
||||||
|
v-bind:height='month.metrics.barHeight'
|
||||||
|
v-bind:x='month.metrics.barX'
|
||||||
|
v-bind:y='month.metrics.barY'></rect>
|
||||||
|
|
||||||
|
{# cumulative dot #}
|
||||||
|
<circle
|
||||||
|
v-if='month.budget'
|
||||||
|
class='budget-chart__cumulative__dot'
|
||||||
|
v-bind:r='month.metrics.cumulativeR'
|
||||||
|
v-bind:cx='month.metrics.cumulativeX'
|
||||||
|
v-bind:cy='month.metrics.cumulativeY'></circle>
|
||||||
|
|
||||||
|
{# abbreviated cumulative label #}
|
||||||
|
<text
|
||||||
|
v-if='month.budget'
|
||||||
|
v-bind:x='month.metrics.cumulativeX'
|
||||||
|
v-bind:y='month.metrics.cumulativeY - 10'
|
||||||
|
text-anchor='middle'
|
||||||
|
class='budget-chart__label'
|
||||||
|
v-html='month.abbreviatedCumulative'></text>
|
||||||
|
|
||||||
|
{# abbreviated spend label #}
|
||||||
|
<text
|
||||||
|
v-bind:x='month.metrics.cumulativeX'
|
||||||
|
v-bind:y='baseHeight + 20'
|
||||||
|
text-anchor='middle'
|
||||||
|
class='budget-chart__label'
|
||||||
|
v-html='"+" + month.abbreviatedSpend'></text>
|
||||||
|
|
||||||
|
{# month label #}
|
||||||
|
<text
|
||||||
|
v-bind:x='month.metrics.cumulativeX'
|
||||||
|
v-bind:y='baseHeight + 40'
|
||||||
|
text-anchor='middle'
|
||||||
|
class='budget-chart__label budget-chart__label--strong'
|
||||||
|
v-html='month.date.month'></text>
|
||||||
|
</g>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{# spend/projected budget path lines #}
|
||||||
|
<path class='budget-chart__projected-path' v-bind:d='projectedPath'></path>
|
||||||
|
<path class='budget-chart__spend-path' v-bind:d='spendPath'></path>
|
||||||
|
|
||||||
|
{# max budget line #}
|
||||||
|
<line
|
||||||
|
class='budget-chart__budget-line'
|
||||||
|
x1='0'
|
||||||
|
v-bind:x2='width'
|
||||||
|
v-bind:y1='budgetHeight'
|
||||||
|
v-bind:y2='budgetHeight'></line>
|
||||||
|
|
||||||
|
<text
|
||||||
|
x='20'
|
||||||
|
v-bind:y='budgetHeight + 20'
|
||||||
|
class='budget-chart__label'>Total Budget</text>
|
||||||
|
<text
|
||||||
|
x='20'
|
||||||
|
v-bind:y='budgetHeight + 40'
|
||||||
|
class='budget-chart__label'
|
||||||
|
v-html='displayBudget'></text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</budget-chart>
|
||||||
|
|
||||||
<div class='spend-table responsive-table-wrapper'>
|
<div class='spend-table responsive-table-wrapper'>
|
||||||
<div class='spend-table__header'>
|
<div class='spend-table__header'>
|
||||||
@ -126,11 +253,11 @@
|
|||||||
|
|
||||||
{% for project_name, project_totals in monthly_totals['projects'].items() %}
|
{% for project_name, project_totals in monthly_totals['projects'].items() %}
|
||||||
<tbody is='toggler' class='spend-table__project'>
|
<tbody is='toggler' class='spend-table__project'>
|
||||||
<template slot-scope='{ isVisible, toggle }'>
|
<template slot-scope='props'>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope='rowgroup'>
|
<th scope='rowgroup'>
|
||||||
<button v-on:click='toggle' class='icon-link icon-link--large spend-table__project__toggler'>
|
<button v-on:click='props.toggle' class='icon-link icon-link--large spend-table__project__toggler'>
|
||||||
<template v-if='isVisible'>{{ Icon('caret_down') }}</template>
|
<template v-if='props.isVisible'>{{ Icon('caret_down') }}</template>
|
||||||
<template v-else>{{ Icon('caret_right') }}</template>
|
<template v-else>{{ Icon('caret_right') }}</template>
|
||||||
{{ project_name }}
|
{{ project_name }}
|
||||||
</button>
|
</button>
|
||||||
@ -145,7 +272,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% for env_name, env_totals in monthly_totals['environments'][project_name].items() %}
|
{% for env_name, env_totals in monthly_totals['environments'][project_name].items() %}
|
||||||
<tr v-show='isVisible'>
|
<tr v-show='props.isVisible'>
|
||||||
<th scope='rowgroup'><a href='#' class='icon-link spend-table__project__env'>{{ Icon('link') }} {{ env_name }}</a></th>
|
<th scope='rowgroup'><a href='#' class='icon-link spend-table__project__env'>{{ Icon('link') }} {{ env_name }}</a></th>
|
||||||
<td class='table-cell--align-right previous-month'>{{ env_totals.get(two_months_ago_index, 0) | dollars }}</td>
|
<td class='table-cell--align-right previous-month'>{{ env_totals.get(two_months_ago_index, 0) | dollars }}</td>
|
||||||
<td class='table-cell--align-right previous-month'>{{ env_totals.get(prev_month_index, 0) | dollars }}</td>
|
<td class='table-cell--align-right previous-month'>{{ env_totals.get(prev_month_index, 0) | dollars }}</td>
|
||||||
|
@ -1811,6 +1811,10 @@ dashdash@^1.12.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
|
date-fns@^1.29.0:
|
||||||
|
version "1.29.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
|
||||||
|
|
||||||
date-now@^0.1.4:
|
date-now@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||||
@ -6271,6 +6275,10 @@ supports-color@^5.3.0, supports-color@^5.4.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^3.0.0"
|
has-flag "^3.0.0"
|
||||||
|
|
||||||
|
svg-innerhtml@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/svg-innerhtml/-/svg-innerhtml-1.1.0.tgz#5e5d1efbc32596479e73a1e8e221d1222678b678"
|
||||||
|
|
||||||
svgo@^0.7.0:
|
svgo@^0.7.0:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
|
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user