feat: dashboard data bind api

This commit is contained in:
Methapon2001 2025-03-05 16:32:41 +07:00
parent 9a196b7077
commit 58b5613d2c
6 changed files with 100 additions and 59 deletions

View file

@ -1,7 +1,8 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js';
import { formatNumberDecimal } from 'src/stores/utils';
defineEmits<{ (e: 'labelClick', value: string, index: number): void }>();
defineEmits<{ (e: 'labelClick', value: string, index: number | null): void }>();
withDefaults(
defineProps<{
@ -38,7 +39,7 @@ withDefaults(
v-if="typeof value === 'string'"
@click="$emit('labelClick', value, null)"
>
{{ value }}
{{ formatNumberDecimal(+value, 2) }}
<q-tooltip v-if="tooltip" :delay="500">{{ value }}</q-tooltip>
</span>
<span v-else :class="{ 'link cursor-pointer': clickable }">

View file

@ -1362,7 +1362,7 @@ export default {
title: 'สรุปยอดใบกำกับภาษี',
caption: 'ตามจำนวนเงินรวม',
total: 'ยอดรวมทั้งหมด',
paid: 'ชำระไม่ครบและชำระครบ',
paid: 'ชำระแล้ว',
pending: 'รอชำระ',
cancel: 'ยกเลิก',
},

View file

@ -14,11 +14,15 @@ import ChartSales from './chart/ChartSales.vue';
import { useNavigator } from 'src/stores/navigator';
import { useQuotationStore } from 'src/stores/quotations';
import { storeToRefs } from 'pinia';
import { useReportStore } from 'src/stores/report';
import { PaymentDataStatus } from 'src/stores/payment/types';
// NOTE: Variable
const navigatorStore = useNavigator();
const quotationStore = useQuotationStore();
const reportStore = useReportStore();
const { stats: quotationStats } = storeToRefs(quotationStore);
const { dataReportPayment } = storeToRefs(reportStore);
const state = reactive({
role: 'admin',
@ -46,6 +50,12 @@ onMounted(async () => {
quotationStats.value = Object.assign(quotationStats.value, ret);
console.log(quotationStats.value);
}
const retPayment = await reportStore.getReportPayment();
if (retPayment) {
dataReportPayment.value = retPayment;
}
});
</script>
<template>
@ -72,27 +82,32 @@ onMounted(async () => {
:dark="$q.dark.isActive"
text-size="10px"
:branch="[
{
icon: 'mdi-account-outline',
count: 0,
label: $t('dashboard.newComer'),
color: 'pink',
},
{
icon: 'material-symbols-light:receipt-long',
count: 0,
count:
(quotationStats.issued || 0) +
(quotationStats.accepted || 0) +
(quotationStats.paymentInProcess || 0) +
(quotationStats.paymentSuccess || 0) +
(quotationStats.processComplete || 0) +
(quotationStats.canceled || 0),
label: $t('dashboard.openQuotation'),
color: 'cyan',
},
{
icon: 'si:wallet-detailed-line',
count: 0,
count:
(quotationStats.paymentSuccess || 0) +
(quotationStats.processComplete || 0),
label: $t('dashboard.paidSales'),
color: 'light-yellow',
},
{
icon: 'fluent:money-hand-24-regular',
count: 0,
count:
(quotationStats.issued || 0) +
(quotationStats.accepted || 0) +
(quotationStats.paymentInProcess || 0),
label: $t('dashboard.pendingSales'),
color: 'light-purple',
},
@ -108,34 +123,57 @@ onMounted(async () => {
<div class="col-sm-8 col-12">
<ChartReceipt
class="full-height"
:summary="[45, 30, 10, 5]"
:summary="[
dataReportPayment.reduce(
(a, c) =>
a +
(c.data[PaymentDataStatus.Success] || 0) +
(c.data[PaymentDataStatus.Wait] || 0),
0,
),
dataReportPayment.reduce(
(a, c) => a + (c.data[PaymentDataStatus.Success] || 0),
0,
),
dataReportPayment.reduce(
(a, c) => a + (c.data[PaymentDataStatus.Wait] || 0),
0,
),
]"
:categories="
dataReportPayment.map((v) => v.month + ' / ' + v.year)
"
:series="[
{
name: $t('dashboard.receipt.total'),
data: [45, 78, 92, 34, 67, 89, 120, 150, 180, 200, 50],
data: dataReportPayment.map(
(v) =>
(v.data[PaymentDataStatus.Success] || 0) +
(v.data[PaymentDataStatus.Wait] || 0),
),
},
{
name: $t('dashboard.receipt.paid'),
data: [30, 50, 70, 20, 40, 65, 80, 130, 160, 190, 210],
data: dataReportPayment.map(
(v) => v.data[PaymentDataStatus.Success] || 0,
),
},
{
name: $t('dashboard.receipt.pending'),
data: [10, 25, 45, 30, 50, 60, 75, 95, 110, 130, 150],
},
{
name: $t('dashboard.receipt.cancel'),
data: [5, 20, 15, 25, 35, 40, 55, 70, 85, 100, 300],
data: dataReportPayment.map(
(v) => v.data[PaymentDataStatus.Wait] || 0,
),
},
]"
/>
</div>
<div class="col-sm-4 col-12">
<!-- <div class="col-sm-4 col-12">
<ChartOpportunity
class="full-height"
:labels="['1', '2', '3', '4', '5', '6', '7', '8']"
:series="[581, 389, 609, 581, 603, 600, 699, 347]"
/>
</div>
</div> -->
<div class="col-sm-4 col-12">
<ChartQuotationStatus
v-if="quotationStats"
@ -148,23 +186,21 @@ onMounted(async () => {
]"
:series="[
{
data: true
? []
: [
quotationStats.issued || 0,
quotationStats.accepted +
quotationStats.paymentInProcess +
quotationStats.paymentSuccess || 1,
quotationStats.processComplete || 1,
quotationStats.canceled || 1,
],
data: [
(quotationStats.issued || 0) +
(quotationStats.accepted || 0),
(quotationStats.paymentInProcess || 0) +
(quotationStats.paymentSuccess || 0),
quotationStats.processComplete || 0,
quotationStats.canceled || 0,
],
},
]"
/>
</div>
<div class="col-sm-8 col-12">
<!-- <div class="col-sm-8 col-12">
<ChartSales class="full-height" />
</div>
</div> -->
</article>
</div>
</section>

View file

@ -6,19 +6,20 @@ import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
const { locale } = useI18n({ useScope: 'global' });
withDefaults(
const prop = withDefaults(
defineProps<{
series: { name: string; data: number[] }[];
summary: number[];
categories: string[];
}>(),
{
series: () => [
{ name: 'series-1', data: [0, 0] },
{ name: 'series-2', data: [1, 1] },
{ name: 'series-3', data: [2, 2] },
{ name: 'series-4', data: [3, 3] },
],
summary: () => [0, 0, 0, 0],
categories: () => ['Jan', 'Feb', 'Mar'],
},
);
const chartOptions = computed(() => {
@ -50,23 +51,7 @@ const chartOptions = computed(() => {
position: 'right',
},
xaxis: {
categories:
locale.value === 'tha'
? thaiMonths
: [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
],
categories: prop.categories,
},
};
});
@ -87,11 +72,6 @@ const detail = [
color: 'var(--orange-4)',
icon: 'material-symbols-light:credit-card-outline',
},
{
label: 'cancel',
color: 'var(--pink-7)',
icon: 'hugeicons:wallet-remove-02',
},
];
</script>
<template>

View file

@ -2,7 +2,13 @@ import { ref } from 'vue';
import { defineStore } from 'pinia';
import { Pagination, Status } from '../types';
import { api } from 'src/boot/axios';
import { Report, ReportProduct, ReportQuotation, ReportSale } from './types';
import {
Report,
ReportPayment,
ReportProduct,
ReportQuotation,
ReportSale,
} from './types';
const ENDPOINT = 'report';
@ -48,12 +54,21 @@ export async function getReportProduct() {
return null;
}
export async function getReportPayment() {
const res = await api.get<ReportPayment[]>(`/${ENDPOINT}/payment`);
if (res.status < 400) {
return res.data;
}
return null;
}
export const useReportStore = defineStore('report-store', () => {
const dataReportQuotation = ref<ReportQuotation[]>([]);
const dataReportInvoice = ref<Report[]>([]);
const dataReportReceipt = ref<Report[]>([]);
const dataReportSale = ref<ReportSale>();
const dataReportProduct = ref<ReportProduct[]>([]);
const dataReportPayment = ref<ReportPayment[]>([]);
return {
dataReportQuotation,
@ -61,11 +76,13 @@ export const useReportStore = defineStore('report-store', () => {
dataReportReceipt,
dataReportSale,
dataReportProduct,
dataReportPayment,
getReportQuotation,
getReportInvoice,
getReportReceipt,
getReportSale,
getReportProduct,
getReportPayment,
};
});

View file

@ -2,6 +2,7 @@ import { QuotationStatus } from 'src/stores/quotations/types';
import { ProductGroup } from '../product-service/types';
import { User } from '../user';
import { CustomerBranch } from '../customer';
import { PaymentDataStatus } from '../payment/types';
export type ReportQuotation = {
updatedAt: Date | null;
@ -34,6 +35,12 @@ export type ReportProduct = {
code: string;
};
export type ReportPayment = {
year: string;
month: string;
data: Partial<Record<PaymentDataStatus, number>>;
};
export type ReportSale = {
byCustomer: (Omit<CustomerBranch, '_count'> & { _count: number })[];
bySale: (User & { _count: number })[];