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

259 lines
7.4 KiB
Vue
Raw Normal View History

<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>