mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
wip: Hardcoded Dashboard
This commit is contained in:
parent
6f79916d1b
commit
d5d00db62c
@ -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>
|
@ -1,126 +1,259 @@
|
||||
<template>
|
||||
<div>
|
||||
<PageHeader title="Dashboard" />
|
||||
<div class="row no-gutters mt-3 mx-3">
|
||||
<DashboardCard v-for="chart in chartData" :key="chart.title" :chartData="chart" />
|
||||
<div class="flex flex-col">
|
||||
<PageHeader>
|
||||
<h1 slot="title" class="text-xl font-bold">{{ _('Dashboard') }}</h1>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageHeader from '../components/PageHeader';
|
||||
import DashboardCard from '../components/DashboardCard';
|
||||
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 {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
PageHeader,
|
||||
DashboardCard
|
||||
SearchBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: [],
|
||||
charts: undefined
|
||||
invoices: [],
|
||||
expenses: []
|
||||
};
|
||||
},
|
||||
async beforeMount() {
|
||||
const { charts } = await frappe.getDoc('DashboardSettings');
|
||||
this.charts = charts;
|
||||
this.charts.forEach(async c => {
|
||||
const { labels, datasets } = await this.getAccountData(c.account, c.type);
|
||||
this.chartData.push({
|
||||
title: c.account,
|
||||
type: c.type.toLowerCase(),
|
||||
color: await frappe.db.getValue('Color', c.color, 'hexvalue'),
|
||||
async mounted() {
|
||||
await this.generateIncomeExpenseChart();
|
||||
this.getInvoiceTotals();
|
||||
this.generateProfitAndLossChart();
|
||||
this.generateExpensesPieChart();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getInvoiceTotals() {
|
||||
let res;
|
||||
res = await frappe.db.sql(`
|
||||
select
|
||||
sum(baseGrandTotal) as total,
|
||||
sum(outstandingAmount) as outstanding
|
||||
from SalesInvoice
|
||||
`);
|
||||
let { total, outstanding } = res[0];
|
||||
this.invoices.push({
|
||||
title: 'Sales Invoices',
|
||||
total: total,
|
||||
unpaid: outstanding,
|
||||
paid: total - outstanding,
|
||||
color: 'blue'
|
||||
});
|
||||
|
||||
res = await frappe.db.sql(`
|
||||
select
|
||||
sum(baseGrandTotal) as total,
|
||||
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'
|
||||
});
|
||||
},
|
||||
async generateIncomeExpenseChart() {
|
||||
let income = await getData({
|
||||
rootType: 'Income',
|
||||
balanceMustBe: 'Credit',
|
||||
fromDate: '2019-01-01',
|
||||
toDate: '2019-12-31',
|
||||
periodicity: 'Monthly'
|
||||
});
|
||||
|
||||
let expense = await getData({
|
||||
rootType: 'Expense',
|
||||
balanceMustBe: 'Debit',
|
||||
fromDate: '2019-01-01',
|
||||
toDate: '2019-12-31',
|
||||
periodicity: 'Monthly'
|
||||
});
|
||||
|
||||
const chart = new Chart(this.$refs['cashflow'], {
|
||||
title: '',
|
||||
type: 'line',
|
||||
animate: false,
|
||||
colors: ['#2490EF', '#B7BFC6'],
|
||||
axisOptions: {
|
||||
xAxisMode: 'tick',
|
||||
shortenYAxisNumbers: true
|
||||
},
|
||||
lineOptions: {
|
||||
regionFill: 1,
|
||||
hideDots: 1,
|
||||
heatLine: 1
|
||||
},
|
||||
data: {
|
||||
labels,
|
||||
datasets
|
||||
labels: income.periodList,
|
||||
datasets: [
|
||||
{
|
||||
name: 'Income',
|
||||
chartType: 'line',
|
||||
values: income.periodList.map(key => income.totalRow[key])
|
||||
},
|
||||
{
|
||||
name: 'Expense',
|
||||
chartType: 'line',
|
||||
values: expense.periodList.map(key => expense.totalRow[key])
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async getAccountData(account, chartType) {
|
||||
let entriesArray = [];
|
||||
let accountType;
|
||||
|
||||
async function getAccountEntries(accountName) {
|
||||
const account = await frappe.getDoc('Account', accountName);
|
||||
accountType = account.rootType;
|
||||
if (account.isGroup != 1) {
|
||||
const ledgerEntries = await frappe.db.getAll({
|
||||
doctype: 'AccountingLedgerEntry',
|
||||
filters: { account: account.name },
|
||||
fields: ['*']
|
||||
});
|
||||
entriesArray = entriesArray.concat(ledgerEntries);
|
||||
} else {
|
||||
const children = await frappe.db.getAll({
|
||||
doctype: 'Account',
|
||||
filters: { parentAccount: account.name }
|
||||
});
|
||||
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) {
|
||||
const currentMonthIndex = new Date().getMonth();
|
||||
const currentYear = new Date().getFullYear();
|
||||
const monthName = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'Aug',
|
||||
'Sept',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
];
|
||||
let labels = [];
|
||||
let datasets = [
|
||||
{
|
||||
type: chartType,
|
||||
values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
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])
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
// 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 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)
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user