hesabixCore/webUI/src/components/widgets/ChequesMonthlyChart.vue

259 lines
7.4 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<v-card class="cheques-monthly-chart" elevation="0" variant="outlined">
<v-card-title class="d-flex align-center justify-space-between pa-4">
<span class="text-h6">
<v-icon left color="primary">mdi-chart-bar</v-icon>
نمودار ماهانه چکها
</span>
<div class="d-flex align-center">
<v-btn-toggle
v-model="chartType"
mandatory
density="compact"
color="primary"
class="mr-2"
>
<v-btn value="count" size="small">تعداد</v-btn>
<v-btn value="amount" size="small">مبلغ</v-btn>
</v-btn-toggle>
<v-btn icon @click="refreshData" :loading="loading">
<v-icon>mdi-refresh</v-icon>
</v-btn>
</div>
</v-card-title>
<v-card-text class="pa-4">
<div v-if="loading" class="text-center py-4">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</div>
<div v-else>
<apexchart
v-if="!loading && series[0].data.length > 0"
ref="barChart"
type="bar"
height="300"
:options="chartOptions"
:series="series"
></apexchart>
<div v-else-if="!loading && series[0].data.length === 0" class="text-center py-4">
<v-icon size="48" color="grey">mdi-chart-bar</v-icon>
<div class="text-body-1 mt-2">داده‌ای برای نمایش وجود ندارد</div>
</div>
<v-divider class="my-4"></v-divider>
<div class="d-flex justify-space-between">
<div class="text-center">
<div class="text-h6 font-weight-bold text-success">
{{ chartType === 'count' ? totalInputCount : $filters.formatNumber(totalInputAmount) }}
</div>
<div class="text-caption">
{{ chartType === 'count' ? 'کل تعداد دریافتی' : 'کل مبلغ دریافتی' }}
</div>
</div>
<div class="text-center">
<div class="text-h6 font-weight-bold text-error">
{{ chartType === 'count' ? totalOutputCount : $filters.formatNumber(totalOutputAmount) }}
</div>
<div class="text-caption">
{{ chartType === 'count' ? 'کل تعداد پرداختی' : 'کل مبلغ پرداختی' }}
</div>
</div>
</div>
</div>
</v-card-text>
</v-card>
</template>
<script>
import VueApexCharts from 'vue3-apexcharts';
import axios from 'axios';
export default {
name: 'ChequesMonthlyChart',
components: {
apexchart: VueApexCharts,
},
data() {
return {
loading: false,
monthlyData: [],
chartType: 'count',
series: [
{
name: 'چک‌های دریافتی',
data: []
},
{
name: 'چک‌های پرداختی',
data: []
}
],
chartCategories: []
};
},
computed: {
chartOptions() {
return {
chart: {
type: 'bar',
stacked: false,
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '55%',
endingShape: 'rounded'
},
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2,
colors: ['transparent']
},
xaxis: {
categories: this.chartCategories,
labels: {
rotate: -45,
rotateAlways: false,
style: {
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
}
}
},
yaxis: {
title: {
text: this.chartType === 'count' ? 'تعداد چک' : 'مبلغ (ریال)',
style: {
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
}
},
labels: {
style: {
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
}
}
},
fill: {
opacity: 1
},
tooltip: {
style: {
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
},
y: {
formatter: (val) => {
if (this.chartType === 'count') {
return val + " چک";
} else {
return this.$filters.formatNumber(val) + " " + this.$t('currency.irr.short');
}
}
}
},
colors: ['#4CAF50', '#F44336'],
legend: {
position: 'top',
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
}
};
},
totalInputAmount() {
return this.monthlyData
.filter(item => item.type === 'input')
.reduce((sum, item) => sum + parseFloat(item.total_amount || 0), 0);
},
totalOutputAmount() {
return this.monthlyData
.filter(item => item.type === 'output')
.reduce((sum, item) => sum + parseFloat(item.total_amount || 0), 0);
},
totalInputCount() {
return this.monthlyData
.filter(item => item.type === 'input')
.reduce((sum, item) => sum + parseInt(item.count || 0), 0);
},
totalOutputCount() {
return this.monthlyData
.filter(item => item.type === 'output')
.reduce((sum, item) => sum + parseInt(item.count || 0), 0);
}
},
watch: {
chartType() {
this.updateChart();
}
},
methods: {
async fetchData() {
this.loading = true;
try {
const response = await axios.post('/api/cheque/dashboard/stats');
this.monthlyData = response.data.monthlyStats || [];
this.updateChart();
} catch (error) {
console.error('Error fetching monthly cheque stats:', error);
this.monthlyData = [];
} finally {
this.loading = false;
}
},
updateChart() {
this.$nextTick(() => {
const months = [...new Set(this.monthlyData.map(item => item.month))].sort();
const inputData = months.map(month => {
const item = this.monthlyData.find(d => d.month === month && d.type === 'input');
if (this.chartType === 'count') {
return item ? parseInt(item.count) : 0;
} else {
return item ? parseFloat(item.total_amount || 0) : 0;
}
});
const outputData = months.map(month => {
const item = this.monthlyData.find(d => d.month === month && d.type === 'output');
if (this.chartType === 'count') {
return item ? parseInt(item.count) : 0;
} else {
return item ? parseFloat(item.total_amount || 0) : 0;
}
});
this.series[0].data = inputData;
this.series[1].data = outputData;
this.chartCategories = months.map(month => {
const [year, monthNum] = month.split('/');
return `${monthNum}/${year}`;
});
});
},
refreshData() {
this.fetchData();
}
},
mounted() {
this.fetchData();
}
};
</script>
<style scoped>
.cheques-monthly-chart {
min-height: 450px;
height: auto;
display: flex;
flex-direction: column;
}
.cheques-monthly-chart .v-card-text {
flex: 1;
display: flex;
flex-direction: column;
}
</style>