259 lines
7.4 KiB
Vue
259 lines
7.4 KiB
Vue
<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> |