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,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)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user