add some widgets about cheque to dashboard
This commit is contained in:
parent
93bdf0fac4
commit
ca043a913f
37
hesabixCore/migrations/Version20250101000000.php
Normal file
37
hesabixCore/migrations/Version20250101000000.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250101000000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add cheque dashboard settings fields';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE dashboard_settings ADD cheques TINYINT(1) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE dashboard_settings ADD cheques_due_today TINYINT(1) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE dashboard_settings ADD cheques_status_chart TINYINT(1) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE dashboard_settings ADD cheques_monthly_chart TINYINT(1) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE dashboard_settings ADD cheques_due_soon TINYINT(1) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE dashboard_settings DROP cheques');
|
||||
$this->addSql('ALTER TABLE dashboard_settings DROP cheques_due_today');
|
||||
$this->addSql('ALTER TABLE dashboard_settings DROP cheques_status_chart');
|
||||
$this->addSql('ALTER TABLE dashboard_settings DROP cheques_monthly_chart');
|
||||
$this->addSql('ALTER TABLE dashboard_settings DROP cheques_due_soon');
|
||||
}
|
||||
}
|
|
@ -892,4 +892,135 @@ class ChequeController extends AbstractController
|
|||
'result' => 'ok'
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/api/cheque/dashboard/stats', name: 'app_cheque_dashboard_stats')]
|
||||
public function app_cheque_dashboard_stats(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, Jdate $jdate): JsonResponse
|
||||
{
|
||||
$acc = $access->hasRole('cheque');
|
||||
if (!$acc)
|
||||
throw $this->createAccessDeniedException();
|
||||
|
||||
$money = $acc['money'];
|
||||
$defaultMoey = $acc['bid']->getMoney();
|
||||
$defMoney = false;
|
||||
if ($defaultMoey->getId() == $money->getId()) {
|
||||
$defMoney = true;
|
||||
}
|
||||
|
||||
// آمار کلی چکها
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$totalInputCheques = $qb->select('COUNT(c.id)')
|
||||
->from(Cheque::class, 'c')
|
||||
->where('c.bid = :bid')
|
||||
->andWhere('c.type = :type')
|
||||
->andWhere($defMoney ? '(c.money = :money OR c.money IS NULL)' : 'c.money = :money')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('type', 'input')
|
||||
->setParameter('money', $money)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$totalOutputCheques = $qb->select('COUNT(c.id)')
|
||||
->from(Cheque::class, 'c')
|
||||
->where('c.bid = :bid')
|
||||
->andWhere('c.type = :type')
|
||||
->andWhere($defMoney ? '(c.money = :money OR c.money IS NULL)' : 'c.money = :money')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('type', 'output')
|
||||
->setParameter('money', $money)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
// چکهای سررسید امروز
|
||||
$today = $jdate->jdate('Y/m/d', time());
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$todayDueCheques = $qb->select('c')
|
||||
->from(Cheque::class, 'c')
|
||||
->where('c.bid = :bid')
|
||||
->andWhere($defMoney ? '(c.money = :money OR c.money IS NULL)' : 'c.money = :money')
|
||||
->andWhere('c.date = :today')
|
||||
->andWhere('c.status != :rejected')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('money', $money)
|
||||
->setParameter('today', $today)
|
||||
->setParameter('rejected', 'برگشت خورده')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// چکهای نزدیک به سررسید (7 روز آینده)
|
||||
$endDate = $jdate->jdate('Y/m/d', strtotime('+7 days'));
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$soonDueCheques = $qb->select('c')
|
||||
->from(Cheque::class, 'c')
|
||||
->where('c.bid = :bid')
|
||||
->andWhere($defMoney ? '(c.money = :money OR c.money IS NULL)' : 'c.money = :money')
|
||||
->andWhere('c.date >= :start')
|
||||
->andWhere('c.date <= :end')
|
||||
->andWhere('c.status != :rejected')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('money', $money)
|
||||
->setParameter('start', $today)
|
||||
->setParameter('end', $endDate)
|
||||
->setParameter('rejected', 'برگشت خورده')
|
||||
->orderBy('c.date', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// آمار وضعیت چکها
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$statusStats = $qb->select('c.status, COUNT(c.id) as count, SUM(c.amount) as total_amount')
|
||||
->from(Cheque::class, 'c')
|
||||
->where('c.bid = :bid')
|
||||
->andWhere($defMoney ? '(c.money = :money OR c.money IS NULL)' : 'c.money = :money')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('money', $money)
|
||||
->groupBy('c.status')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// آمار ماهانه چکها (6 ماه اخیر) - سادهسازی شده
|
||||
$sixMonthsAgo = $jdate->jdate('Y/m', strtotime('-6 months')) . '/01';
|
||||
$qb = $entityManager->createQueryBuilder();
|
||||
$allCheques = $qb->select('c.date, c.type, c.amount')
|
||||
->from(Cheque::class, 'c')
|
||||
->where('c.bid = :bid')
|
||||
->andWhere($defMoney ? '(c.money = :money OR c.money IS NULL)' : 'c.money = :money')
|
||||
->andWhere('c.date >= :sixMonthsAgo')
|
||||
->setParameter('bid', $acc['bid'])
|
||||
->setParameter('money', $money)
|
||||
->setParameter('sixMonthsAgo', $sixMonthsAgo)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// پردازش دادههای ماهانه
|
||||
$processedMonthlyStats = [];
|
||||
foreach ($allCheques as $cheque) {
|
||||
$month = substr($cheque['date'], 0, 7); // YYYY/MM
|
||||
$key = $month . '_' . $cheque['type'];
|
||||
|
||||
if (!isset($processedMonthlyStats[$key])) {
|
||||
$processedMonthlyStats[$key] = [
|
||||
'month' => $month,
|
||||
'type' => $cheque['type'],
|
||||
'count' => 0,
|
||||
'total_amount' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$processedMonthlyStats[$key]['count']++;
|
||||
$processedMonthlyStats[$key]['total_amount'] += (float)($cheque['amount'] ?? 0);
|
||||
}
|
||||
|
||||
$monthlyStats = array_values($processedMonthlyStats);
|
||||
|
||||
return $this->json([
|
||||
'totalInputCheques' => $totalInputCheques,
|
||||
'totalOutputCheques' => $totalOutputCheques,
|
||||
'todayDueCheques' => Explore::SerializeCheques($todayDueCheques),
|
||||
'soonDueCheques' => Explore::SerializeCheques($soonDueCheques),
|
||||
'statusStats' => $statusStats,
|
||||
'monthlyStats' => $monthlyStats
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,11 @@ class DashboardController extends AbstractController
|
|||
if(array_key_exists('topCostCenters',$params)) $setting->setTopCostCenters($params['topCostCenters']);
|
||||
if(array_key_exists('incomes',$params)) $setting->setIncomes($params['incomes']);
|
||||
if(array_key_exists('topIncomeCenters',$params)) $setting->setTopIncomesChart($params['topIncomeCenters']);
|
||||
if(array_key_exists('cheques',$params)) $setting->setCheques($params['cheques']);
|
||||
if(array_key_exists('chequesDueToday',$params)) $setting->setChequesDueToday($params['chequesDueToday']);
|
||||
if(array_key_exists('chequesStatusChart',$params)) $setting->setChequesStatusChart($params['chequesStatusChart']);
|
||||
if(array_key_exists('chequesMonthlyChart',$params)) $setting->setChequesMonthlyChart($params['chequesMonthlyChart']);
|
||||
if(array_key_exists('chequesDueSoon',$params)) $setting->setChequesDueSoon($params['chequesDueSoon']);
|
||||
|
||||
$entityManagerInterface->persist($setting);
|
||||
$entityManagerInterface->flush();
|
||||
|
|
|
@ -66,6 +66,21 @@ class DashboardSettings
|
|||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $topIncomesChart = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $cheques = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $chequesDueToday = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $chequesStatusChart = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $chequesMonthlyChart = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $chequesDueSoon = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -274,4 +289,64 @@ class DashboardSettings
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isCheques(): ?bool
|
||||
{
|
||||
return $this->cheques;
|
||||
}
|
||||
|
||||
public function setCheques(?bool $cheques): static
|
||||
{
|
||||
$this->cheques = $cheques;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isChequesDueToday(): ?bool
|
||||
{
|
||||
return $this->chequesDueToday;
|
||||
}
|
||||
|
||||
public function setChequesDueToday(?bool $chequesDueToday): static
|
||||
{
|
||||
$this->chequesDueToday = $chequesDueToday;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isChequesStatusChart(): ?bool
|
||||
{
|
||||
return $this->chequesStatusChart;
|
||||
}
|
||||
|
||||
public function setChequesStatusChart(?bool $chequesStatusChart): static
|
||||
{
|
||||
$this->chequesStatusChart = $chequesStatusChart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isChequesMonthlyChart(): ?bool
|
||||
{
|
||||
return $this->chequesMonthlyChart;
|
||||
}
|
||||
|
||||
public function setChequesMonthlyChart(?bool $chequesMonthlyChart): static
|
||||
{
|
||||
$this->chequesMonthlyChart = $chequesMonthlyChart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isChequesDueSoon(): ?bool
|
||||
{
|
||||
return $this->chequesDueSoon;
|
||||
}
|
||||
|
||||
public function setChequesDueSoon(?bool $chequesDueSoon): static
|
||||
{
|
||||
$this->chequesDueSoon = $chequesDueSoon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -326,7 +326,7 @@ class Explore
|
|||
'id' => $person->getId(),
|
||||
'code' => $person->getCode(),
|
||||
'nikename' => $person->getNikename(),
|
||||
'name' => $person->getName(),
|
||||
'name' => $person->getName() ?: $person->getNikename(),
|
||||
'tel' => $person->getTel(),
|
||||
'mobile' => $person->getmobile(),
|
||||
'mobile2' => $person->getMobile2(),
|
||||
|
@ -633,6 +633,11 @@ class Explore
|
|||
'topCostCenters' => $item->isTopCostCenters(),
|
||||
'incomes' => $item->isIncomes(),
|
||||
'topIncomeCenters' => $item->isTopIncomesChart(),
|
||||
'cheques' => $item->isCheques(),
|
||||
'chequesDueToday' => $item->isChequesDueToday(),
|
||||
'chequesStatusChart' => $item->isChequesStatusChart(),
|
||||
'chequesMonthlyChart' => $item->isChequesMonthlyChart(),
|
||||
'chequesDueSoon' => $item->isChequesDueSoon(),
|
||||
];
|
||||
if ($result['topCommodities'] === null)
|
||||
$result['topCommodities'] = true;
|
||||
|
@ -664,6 +669,16 @@ class Explore
|
|||
$result['incomes'] = true;
|
||||
if ($result['topIncomeCenters'] === null)
|
||||
$result['topIncomeCenters'] = true;
|
||||
if ($result['cheques'] === null)
|
||||
$result['cheques'] = true;
|
||||
if ($result['chequesDueToday'] === null)
|
||||
$result['chequesDueToday'] = true;
|
||||
if ($result['chequesStatusChart'] === null)
|
||||
$result['chequesStatusChart'] = true;
|
||||
if ($result['chequesMonthlyChart'] === null)
|
||||
$result['chequesMonthlyChart'] = true;
|
||||
if ($result['chequesDueSoon'] === null)
|
||||
$result['chequesDueSoon'] = true;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
173
webUI/src/components/widgets/ChequesDueSoonWidget.vue
Normal file
173
webUI/src/components/widgets/ChequesDueSoonWidget.vue
Normal file
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<v-card class="cheques-due-soon-widget" 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="orange">mdi-clock-alert</v-icon>
|
||||
چکهای نزدیک به سررسید
|
||||
</span>
|
||||
<v-chip :color="soonDueCheques.length > 0 ? 'orange' : 'success'" size="small">
|
||||
{{ soonDueCheques.length }} چک
|
||||
</v-chip>
|
||||
</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-if="soonDueCheques.length === 0" class="text-center py-4">
|
||||
<v-icon size="48" color="success">mdi-check-circle</v-icon>
|
||||
<div class="text-body-1 mt-2">هیچ چکی در 7 روز آینده سررسید ندارد</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="cheque in soonDueCheques.slice(0, 5)"
|
||||
:key="cheque.id"
|
||||
:class="getDueClass(cheque.date)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:color="cheque.type === 'input' ? 'success' : 'error'"
|
||||
size="small"
|
||||
>
|
||||
{{ cheque.type === 'input' ? 'mdi-arrow-down' : 'mdi-arrow-up' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
|
||||
<v-list-item-title class="text-body-2 text-truncate">
|
||||
{{ cheque.number }} - {{ cheque.bankOncheque }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-caption text-truncate">
|
||||
{{ cheque.person?.name || 'نامشخص' }} - {{ $filters.formatNumber(cheque.amount) }} {{ $t('currency.irr.short') }}
|
||||
</v-list-item-subtitle>
|
||||
<template v-slot:append>
|
||||
<v-chip
|
||||
:color="getDueChipColor(cheque.date)"
|
||||
size="x-small"
|
||||
variant="outlined"
|
||||
>
|
||||
{{ getDaysUntilDue(cheque.date) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<div v-if="soonDueCheques.length > 5" class="text-center mt-2">
|
||||
<v-btn variant="text" size="small" color="primary">
|
||||
مشاهده {{ soonDueCheques.length - 5 }} چک دیگر
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment-jalaali';
|
||||
|
||||
export default {
|
||||
name: 'ChequesDueSoonWidget',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
soonDueCheques: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await axios.post('/api/cheque/dashboard/stats');
|
||||
this.soonDueCheques = response.data.soonDueCheques || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching soon due cheques:', error);
|
||||
this.soonDueCheques = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
convertShamsiToMiladi(shamsiDate) {
|
||||
if (!shamsiDate) return new Date();
|
||||
|
||||
try {
|
||||
// استفاده از moment-jalaali برای تبدیل دقیق
|
||||
const jMoment = moment(shamsiDate, 'jYYYY/jM/jD');
|
||||
return jMoment.toDate();
|
||||
} catch (error) {
|
||||
console.error('Error converting date:', error);
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
getDaysUntilDue(date) {
|
||||
const today = new Date();
|
||||
const dueDate = this.convertShamsiToMiladi(date);
|
||||
const diffTime = dueDate - today;
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) return 'امروز';
|
||||
if (diffDays === 1) return 'فردا';
|
||||
if (diffDays < 0) return `${Math.abs(diffDays)} روز گذشته`;
|
||||
return `${diffDays} روز`;
|
||||
},
|
||||
getDueChipColor(date) {
|
||||
const today = new Date();
|
||||
const dueDate = this.convertShamsiToMiladi(date);
|
||||
const diffTime = dueDate - today;
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays <= 0) return 'error';
|
||||
if (diffDays <= 3) return 'warning';
|
||||
return 'info';
|
||||
},
|
||||
getDueClass(date) {
|
||||
const today = new Date();
|
||||
const dueDate = this.convertShamsiToMiladi(date);
|
||||
const diffTime = dueDate - today;
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays <= 0) return 'border-left-error';
|
||||
if (diffDays <= 3) return 'border-left-warning';
|
||||
return 'border-left-info';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cheques-due-soon-widget {
|
||||
min-height: 300px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cheques-due-soon-widget .v-card-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.border-left-error {
|
||||
border-left: 3px solid #F44336;
|
||||
}
|
||||
|
||||
.border-left-warning {
|
||||
border-left: 3px solid #FF9800;
|
||||
}
|
||||
|
||||
.border-left-info {
|
||||
border-left: 3px solid #2196F3;
|
||||
}
|
||||
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
116
webUI/src/components/widgets/ChequesDueTodayWidget.vue
Normal file
116
webUI/src/components/widgets/ChequesDueTodayWidget.vue
Normal file
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<v-card class="cheques-due-today-widget" 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="warning">mdi-calendar-alert</v-icon>
|
||||
چکهای سررسید امروز
|
||||
</span>
|
||||
<v-chip :color="todayDueCheques.length > 0 ? 'warning' : 'success'" size="small">
|
||||
{{ todayDueCheques.length }} چک
|
||||
</v-chip>
|
||||
</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-if="todayDueCheques.length === 0" class="text-center py-4">
|
||||
<v-icon size="48" color="success">mdi-check-circle</v-icon>
|
||||
<div class="text-body-1 mt-2">هیچ چکی امروز سررسید ندارد</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="cheque in todayDueCheques.slice(0, 5)"
|
||||
:key="cheque.id"
|
||||
:class="cheque.type === 'input' ? 'border-left-success' : 'border-left-error'"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon
|
||||
:color="cheque.type === 'input' ? 'success' : 'error'"
|
||||
size="small"
|
||||
>
|
||||
{{ cheque.type === 'input' ? 'mdi-arrow-down' : 'mdi-arrow-up' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
|
||||
<v-list-item-title class="text-body-2 text-truncate">
|
||||
{{ cheque.number }} - {{ cheque.bankOncheque }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-caption text-truncate">
|
||||
{{ cheque.person?.name || 'نامشخص' }} - {{ $filters.formatNumber(cheque.amount) }} {{ $t('currency.irr.short') }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<div v-if="todayDueCheques.length > 5" class="text-center mt-2">
|
||||
<v-btn variant="text" size="small" color="primary">
|
||||
مشاهده {{ todayDueCheques.length - 5 }} چک دیگر
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'ChequesDueTodayWidget',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
todayDueCheques: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await axios.post('/api/cheque/dashboard/stats');
|
||||
this.todayDueCheques = response.data.todayDueCheques || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching today due cheques:', error);
|
||||
this.todayDueCheques = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cheques-due-today-widget {
|
||||
min-height: 300px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cheques-due-today-widget .v-card-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.border-left-success {
|
||||
border-left: 3px solid #4CAF50;
|
||||
}
|
||||
|
||||
.border-left-error {
|
||||
border-left: 3px solid #F44336;
|
||||
}
|
||||
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
259
webUI/src/components/widgets/ChequesMonthlyChart.vue
Normal file
259
webUI/src/components/widgets/ChequesMonthlyChart.vue
Normal file
|
@ -0,0 +1,259 @@
|
|||
<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>
|
237
webUI/src/components/widgets/ChequesStatusChart.vue
Normal file
237
webUI/src/components/widgets/ChequesStatusChart.vue
Normal file
|
@ -0,0 +1,237 @@
|
|||
<template>
|
||||
<v-card class="cheques-status-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="info">mdi-chart-pie</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.length > 0"
|
||||
ref="pieChart"
|
||||
type="pie"
|
||||
height="250"
|
||||
:options="chartOptions"
|
||||
:series="series"
|
||||
></apexchart>
|
||||
<div v-else-if="!loading && series.length === 0" class="text-center py-4">
|
||||
<v-icon size="48" color="grey">mdi-chart-pie</v-icon>
|
||||
<div class="text-body-1 mt-2">دادهای برای نمایش وجود ندارد</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
<div class="status-legend">
|
||||
<div
|
||||
v-for="(item, index) in statusData"
|
||||
:key="index"
|
||||
class="d-flex justify-space-between align-center mb-3"
|
||||
>
|
||||
<div class="d-flex align-center flex-1 min-width-0">
|
||||
<div
|
||||
class="status-color mr-2 flex-shrink-0"
|
||||
:style="{ backgroundColor: getStatusColor(item.status) }"
|
||||
></div>
|
||||
<span class="text-body-2 text-truncate">{{ item.status }}</span>
|
||||
</div>
|
||||
<div class="text-right flex-shrink-0 ml-2">
|
||||
<div class="text-body-2 font-weight-bold">
|
||||
{{ chartType === 'count' ? item.count : $filters.formatNumber(item.total_amount || 0) }}
|
||||
</div>
|
||||
<div class="text-caption text-grey">
|
||||
{{ chartType === 'count' ? 'چک' : $t('currency.irr.short') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueApexCharts from 'vue3-apexcharts';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'ChequesStatusChart',
|
||||
components: {
|
||||
apexchart: VueApexCharts,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
statusData: [],
|
||||
series: [],
|
||||
chartLabels: [],
|
||||
chartType: 'count'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
chartOptions() {
|
||||
return {
|
||||
chart: {
|
||||
type: 'pie',
|
||||
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
|
||||
},
|
||||
labels: this.chartLabels,
|
||||
colors: ['#4CAF50', '#FF9800', '#F44336', '#2196F3', '#9C27B0'],
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responsive: [{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 200,
|
||||
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
|
||||
},
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
fontFamily: "'Vazirmatn FD', Arial, sans-serif",
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartType() {
|
||||
this.updateChart();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await axios.post('/api/cheque/dashboard/stats');
|
||||
this.statusData = response.data.statusStats || [];
|
||||
this.updateChart();
|
||||
} catch (error) {
|
||||
console.error('Error fetching cheque status stats:', error);
|
||||
this.statusData = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
updateChart() {
|
||||
this.$nextTick(() => {
|
||||
if (this.chartType === 'count') {
|
||||
this.series = this.statusData.map(item => item.count);
|
||||
} else {
|
||||
this.series = this.statusData.map(item => parseFloat(item.total_amount || 0));
|
||||
}
|
||||
this.chartLabels = this.statusData.map(item => item.status);
|
||||
});
|
||||
},
|
||||
getStatusColor(status) {
|
||||
const colorMap = {
|
||||
'وصول': '#4CAF50',
|
||||
'وصول نشده': '#FF9800',
|
||||
'برگشت خورده': '#F44336',
|
||||
'پاس شده': '#2196F3',
|
||||
'پاس نشده': '#9C27B0'
|
||||
};
|
||||
return colorMap[status] || '#757575';
|
||||
},
|
||||
refreshData() {
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cheques-status-chart {
|
||||
min-height: 400px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cheques-status-chart .v-card-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.status-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-legend {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.status-legend::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.status-legend::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.status-legend::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.status-legend::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
|
||||
.min-width-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.text-truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
99
webUI/src/components/widgets/ChequesSummaryWidget.vue
Normal file
99
webUI/src/components/widgets/ChequesSummaryWidget.vue
Normal file
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<v-card class="cheques-summary-widget" 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-credit-card</v-icon>
|
||||
خلاصه چکها
|
||||
</span>
|
||||
<v-btn icon @click="refreshData" :loading="loading">
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div class="text-center">
|
||||
<div class="text-h4 font-weight-bold text-success">
|
||||
{{ $filters.formatNumber(chequeStats.totalInputCheques) }}
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
چکهای دریافتی
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div class="text-center">
|
||||
<div class="text-h4 font-weight-bold text-error">
|
||||
{{ $filters.formatNumber(chequeStats.totalOutputCheques) }}
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
چکهای پرداختی
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-4"></v-divider>
|
||||
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<span class="text-body-2">کل چکها:</span>
|
||||
<span class="text-h6 font-weight-bold">
|
||||
{{ $filters.formatNumber(chequeStats.totalInputCheques + chequeStats.totalOutputCheques) }}
|
||||
</span>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'ChequesSummaryWidget',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
chequeStats: {
|
||||
totalInputCheques: 0,
|
||||
totalOutputCheques: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await axios.post('/api/cheque/dashboard/stats');
|
||||
this.chequeStats = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching cheque stats:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
refreshData() {
|
||||
this.fetchData();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cheques-summary-widget {
|
||||
min-height: 200px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cheques-summary-widget .v-card-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
|
@ -16,7 +16,7 @@
|
|||
</v-tab>
|
||||
<v-tab value="output" block>
|
||||
<v-icon start>mdi-file-import</v-icon>
|
||||
چکهای واگذار شده
|
||||
چکهای پرداختی
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
|
|
@ -55,6 +55,27 @@
|
|||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- ویجتهای چک -->
|
||||
<v-col cols="12" sm="6" md="4" v-show="permissions.cheque && dashboard.cheques">
|
||||
<cheques-summary-widget class="animate__animated animate__zoomIn" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" md="4" v-show="permissions.cheque && dashboard.chequesDueToday">
|
||||
<cheques-due-today-widget class="animate__animated animate__zoomIn" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" md="4" v-show="permissions.cheque && dashboard.chequesDueSoon">
|
||||
<cheques-due-soon-widget class="animate__animated animate__zoomIn" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12" md="6" v-show="permissions.cheque && dashboard.chequesStatusChart">
|
||||
<cheques-status-chart class="animate__animated animate__zoomIn" />
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="12" md="6" v-show="permissions.cheque && dashboard.chequesMonthlyChart">
|
||||
<cheques-monthly-chart class="animate__animated animate__zoomIn" />
|
||||
</v-col>
|
||||
<!-- کارتهای قبلی (بدون تغییر) -->
|
||||
<v-col cols="12" sm="6" md="4" v-show="permissions.wallet && dashboard.wallet">
|
||||
<v-card class="animate__animated animate__zoomIn card-equal-height" color="success-lighten-4" variant="elevated"
|
||||
|
@ -212,6 +233,12 @@
|
|||
inset class="text-caption" />
|
||||
<v-switch color="primary" :label="$t('dashboard.incomes.centers')" v-model="dashboard.topIncomeCenters"
|
||||
hide-details inset class="text-caption" />
|
||||
<v-switch color="primary" label="خلاصه چکها" v-model="dashboard.cheques" hide-details inset
|
||||
class="text-caption" />
|
||||
<v-switch color="primary" label="چکهای سررسید امروز" v-model="dashboard.chequesDueToday" hide-details inset
|
||||
class="text-caption" />
|
||||
<v-switch color="primary" label="چکهای نزدیک به سررسید" v-model="dashboard.chequesDueSoon" hide-details inset
|
||||
class="text-caption" />
|
||||
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
|
@ -235,6 +262,10 @@
|
|||
hide-details inset class="text-caption" />
|
||||
<v-switch color="primary" :label="$t('drawer.notif')" v-model="dashboard.notif"
|
||||
:hint="$t('dialog.notif_msg')" persistent-hint inset class="text-caption" />
|
||||
<v-switch color="primary" label="نمودار وضعیت چکها" v-model="dashboard.chequesStatusChart" hide-details inset
|
||||
class="text-caption" />
|
||||
<v-switch color="primary" label="نمودار ماهانه چکها" v-model="dashboard.chequesMonthlyChart" hide-details inset
|
||||
class="text-caption" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
|
@ -257,6 +288,11 @@ import SaleChart from "./component/widgets/saleChart.vue";
|
|||
import TopCommoditiesChart from '@/components/widgets/TopCommoditiesChart.vue';
|
||||
import TopCostCentersChart from '@/components/widgets/TopCostCentersChart.vue';
|
||||
import TopIncomeCentersChart from '@/components/widgets/TopIncomeCentersChart.vue';
|
||||
import ChequesSummaryWidget from '@/components/widgets/ChequesSummaryWidget.vue';
|
||||
import ChequesDueTodayWidget from '@/components/widgets/ChequesDueTodayWidget.vue';
|
||||
import ChequesStatusChart from '@/components/widgets/ChequesStatusChart.vue';
|
||||
import ChequesMonthlyChart from '@/components/widgets/ChequesMonthlyChart.vue';
|
||||
import ChequesDueSoonWidget from '@/components/widgets/ChequesDueSoonWidget.vue';
|
||||
|
||||
export default {
|
||||
name: "dashboard",
|
||||
|
@ -265,6 +301,11 @@ export default {
|
|||
TopCommoditiesChart,
|
||||
TopCostCentersChart,
|
||||
TopIncomeCentersChart,
|
||||
ChequesSummaryWidget,
|
||||
ChequesDueTodayWidget,
|
||||
ChequesStatusChart,
|
||||
ChequesMonthlyChart,
|
||||
ChequesDueSoonWidget,
|
||||
},
|
||||
data() {
|
||||
const self = this;
|
||||
|
@ -294,6 +335,11 @@ export default {
|
|||
topCostCenters: false,
|
||||
incomes: false,
|
||||
topIncomeCenters: false,
|
||||
cheques: false,
|
||||
chequesDueToday: false,
|
||||
chequesStatusChart: false,
|
||||
chequesMonthlyChart: false,
|
||||
chequesDueSoon: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue