2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 11:29:00 +00:00

wip: Hardcoded Dashboard

This commit is contained in:
Faris Ansari 2019-11-09 01:29:11 +05:30
parent 6f79916d1b
commit d5d00db62c
2 changed files with 234 additions and 127 deletions

View File

@ -1,26 +0,0 @@
<template>
<div class="col-6">
<div class="card mx-3 my-3">
<div class="card-body">
<div :ref="chartData.title"></div>
</div>
</div>
</div>
</template>
<script>
import { Chart } from 'frappe-charts';
export default {
name: 'DashboardCard',
props: ['chartData'],
mounted() {
const chart = new Chart(this.$refs[this.chartData.title], {
title: this.chartData.title,
data: this.chartData.data,
type: this.chartData.type, // or 'bar', 'line', 'scatter', 'pie', 'percentage'
height: 250,
colors: [this.chartData.color]
});
}
};
</script>

View File

@ -1,125 +1,258 @@
<template> <template>
<div> <div class="flex flex-col">
<PageHeader title="Dashboard" /> <PageHeader>
<div class="row no-gutters mt-3 mx-3"> <h1 slot="title" class="text-xl font-bold">{{ _('Dashboard') }}</h1>
<DashboardCard v-for="chart in chartData" :key="chart.title" :chartData="chart" /> <template slot="actions">
<SearchBar class="ml-2" />
</template>
</PageHeader>
<div class="mt-4 px-8">
<div class="border-t" />
<div class="mt-6">
<div class="font-medium">Cash Flow</div>
<div ref="cashflow"></div>
</div>
<div class="my-10 border-t" />
<div class="flex -mx-4">
<div class="w-1/2 px-4" v-for="invoice in invoices" :key="invoice.title">
<div class="font-medium">{{ invoice.title }}</div>
<div class="mt-6 flex justify-between">
<div class="text-sm">
{{ invoice.paid }}
<span class="text-gray-600">{{ _('Paid') }}</span>
</div>
<div class="text-sm">
{{ invoice.unpaid }}
<span class="text-gray-600">{{ _('Unpaid') }}</span>
</div>
</div>
<div class="mt-2 relative">
<div
class="w-full h-4 rounded"
:class="invoice.color == 'blue' ? 'bg-blue-200' : 'bg-gray-200'"
></div>
<div
class="absolute inset-0 h-4 rounded"
:class="invoice.color == 'blue' ? 'bg-blue-500' : 'bg-gray-500'"
:style="`width: ${invoice.paid / invoice.total * 100}%`"
></div>
</div>
</div>
</div>
<div class="my-10 border-t" />
<div class="flex -mx-4">
<div class="w-1/2 px-4">
<span class="font-medium">Profit and Loss</span>
<div ref="profit-and-loss"></div>
</div>
<div class="w-1/2 px-4">
<span class="font-medium">Top Expenses</span>
<div class="flex">
<div class="w-1/2">
<div
class="mt-5 flex justify-between items-center text-sm"
v-for="d in expenses"
:key="d.name"
>
<div class="flex items-center">
<div class="w-3 h-3 rounded" :style="`backgroundColor: ${d.color}`"></div>
<div class="ml-3">{{ d.name }}</div>
</div>
<div>{{ d.value }}</div>
</div>
</div>
<div class="w-1/2" ref="top-expenses"></div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import PageHeader from '../components/PageHeader';
import DashboardCard from '../components/DashboardCard';
import frappe from 'frappejs'; import frappe from 'frappejs';
import { Chart } from 'frappe-charts';
import PageHeader from '@/components/PageHeader';
import SearchBar from '@/components/SearchBar';
import { getData } from '../../reports/FinancialStatements/FinancialStatements';
import ProfitAndLoss from '../../reports/ProfitAndLoss/ProfitAndLoss';
import theme from '@/theme';
export default { export default {
name: 'Dashboard', name: 'Dashboard',
components: { components: {
PageHeader, PageHeader,
DashboardCard SearchBar,
}, },
data() { data() {
return { return {
chartData: [], invoices: [],
charts: undefined expenses: []
}; };
}, },
async beforeMount() { async mounted() {
const { charts } = await frappe.getDoc('DashboardSettings'); await this.generateIncomeExpenseChart();
this.charts = charts; this.getInvoiceTotals();
this.charts.forEach(async c => { this.generateProfitAndLossChart();
const { labels, datasets } = await this.getAccountData(c.account, c.type); this.generateExpensesPieChart();
this.chartData.push({
title: c.account,
type: c.type.toLowerCase(),
color: await frappe.db.getValue('Color', c.color, 'hexvalue'),
data: {
labels,
datasets
}
});
});
}, },
methods: { methods: {
async getAccountData(account, chartType) { async getInvoiceTotals() {
let entriesArray = []; let res;
let accountType; res = await frappe.db.sql(`
select
async function getAccountEntries(accountName) { sum(baseGrandTotal) as total,
const account = await frappe.getDoc('Account', accountName); sum(outstandingAmount) as outstanding
accountType = account.rootType; from SalesInvoice
if (account.isGroup != 1) { `);
const ledgerEntries = await frappe.db.getAll({ let { total, outstanding } = res[0];
doctype: 'AccountingLedgerEntry', this.invoices.push({
filters: { account: account.name }, title: 'Sales Invoices',
fields: ['*'] total: total,
unpaid: outstanding,
paid: total - outstanding,
color: 'blue'
}); });
entriesArray = entriesArray.concat(ledgerEntries);
} else { res = await frappe.db.sql(`
const children = await frappe.db.getAll({ select
doctype: 'Account', sum(baseGrandTotal) as total,
filters: { parentAccount: account.name } sum(outstandingAmount) as outstanding
from PurchaseInvoice
`);
let { total: purchaseTotal, outstanding: purchaseOutstanding } = res[0];
this.invoices.push({
title: 'Purchase Invoices',
total: purchaseTotal,
unpaid: purchaseOutstanding,
paid: purchaseTotal - purchaseOutstanding,
color: 'gray'
}); });
if (children.length)
for (let account of children) {
entriesArray = await getAccountEntries(account.name);
}
}
return entriesArray;
}
let ledgerEntries = await getAccountEntries(account);
accountType = ['Asset', 'Expense'].includes(accountType)
? 'debit'
: 'credit';
let { labels, datasets } = this.createLabelsAndDataSet(chartType);
const currentMonthIndex = new Date().getMonth();
const currentYear = new Date().getFullYear();
for (let entry of ledgerEntries) {
let monthIndex = parseInt(entry.date.split('-')[1]) - 1;
let year = parseInt(entry.date.split('-')[0]);
let pos = 12 - (currentMonthIndex - monthIndex);
if (pos <= 0) {
pos = pos === 0 ? (year === currentYear ? 12 : 0) : Math.abs(pos);
}
if (accountType === 'debit')
datasets[0].values[pos] += entry.debit || -entry.credit;
else if (accountType === 'credit')
datasets[0].values[pos] += -entry.debit || entry.credit;
}
return { labels, datasets };
}, },
createLabelsAndDataSet(chartType) { async generateIncomeExpenseChart() {
const currentMonthIndex = new Date().getMonth(); let income = await getData({
const currentYear = new Date().getFullYear(); rootType: 'Income',
const monthName = [ balanceMustBe: 'Credit',
'Jan', fromDate: '2019-01-01',
'Feb', toDate: '2019-12-31',
'Mar', periodicity: 'Monthly'
'Apr', });
'May',
'June', let expense = await getData({
'July', rootType: 'Expense',
'Aug', balanceMustBe: 'Debit',
'Sept', fromDate: '2019-01-01',
'Oct', toDate: '2019-12-31',
'Nov', periodicity: 'Monthly'
'Dec' });
];
let labels = []; const chart = new Chart(this.$refs['cashflow'], {
let datasets = [ title: '',
type: 'line',
animate: false,
colors: ['#2490EF', '#B7BFC6'],
axisOptions: {
xAxisMode: 'tick',
shortenYAxisNumbers: true
},
lineOptions: {
regionFill: 1,
hideDots: 1,
heatLine: 1
},
data: {
labels: income.periodList,
datasets: [
{ {
type: chartType, name: 'Income',
values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] chartType: 'line',
values: income.periodList.map(key => income.totalRow[key])
},
{
name: 'Expense',
chartType: 'line',
values: expense.periodList.map(key => expense.totalRow[key])
} }
]; ]
// Arrange month labels according to current month.
for (let i = 0; i < 13; i++) {
let year = i + currentMonthIndex >= 12 ? currentYear : currentYear - 1;
labels.push(monthName[(i + currentMonthIndex) % 12]);
} }
return { labels, datasets }; });
},
async generateProfitAndLossChart() {
let pl = new ProfitAndLoss();
let res = await pl.run({
fromDate: '2019-01-01',
toDate: '2019-12-31',
periodicity: 'Monthly'
});
let totalRow = res.rows[res.rows.length - 1];
const chart = new Chart(this.$refs['profit-and-loss'], {
title: '',
animate: false,
type: 'bar',
colors: ['#2490EF', '#B7BFC6'],
axisOptions: {
xAxisMode: 'tick',
shortenYAxisNumbers: true
},
data: {
labels: res.columns.map(d => d.replace('2019', '')),
datasets: [
{
name: 'Income',
chartType: 'bar',
values: res.columns.map(key => totalRow[key])
}
]
}
});
},
async generateExpensesPieChart() {
let expense = await getData({
rootType: 'Expense',
balanceMustBe: 'Debit',
fromDate: '2019-01-01',
toDate: '2019-12-31',
periodicity: 'Yearly'
});
// let key = expense.periodList[0];
let key = '2019 - 2020';
let shades = ['800', '600', '400', '200', '100'];
let expenses = expense.accounts
.filter(d => d[key])
.map((d, i) => {
return {
name: d.name,
value: d[key]
};
})
.sort((a, b) => {
return b.value - a.value;
})
.slice(0, 5)
.map((d, i) => {
d.color = theme.backgroundColor.gray[shades[i]];
return d;
});
this.expenses = expenses;
let chart = new Chart(this.$refs['top-expenses'], {
type: 'pie',
colors: expenses.map(d => d.color),
data: {
labels: expenses.map(d => d.name),
datasets: [
{
values: expenses.map(d => d.value)
}
]
}
});
} }
} }
}; };