Preserve the local Ant UI work across views/components and add the new home dashboard chart panels before merging latest main.
103 lines
4.4 KiB
Vue
103 lines
4.4 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { toFaDigits } from '@/lib/jalali';
|
|
import type { HomeDashboardSalesSummaryDto } from '@mento/shared';
|
|
import UiChart, { type UiChartOption } from '@/components/ui/UiChart.vue';
|
|
|
|
const props = defineProps<{
|
|
summary: HomeDashboardSalesSummaryDto;
|
|
formatAmount: (value: number) => string;
|
|
}>();
|
|
|
|
const slices = computed(() => [
|
|
{ name: 'باز', value: props.summary.openCount, color: '#3b82f6' },
|
|
{ name: 'برنده', value: props.summary.wonCount, color: '#10b981' },
|
|
{ name: 'باخته', value: props.summary.lostCount, color: '#f43f5e' },
|
|
]);
|
|
|
|
const total = computed(() => slices.value.reduce((a, s) => a + s.value, 0));
|
|
const hasData = computed(() => total.value > 0);
|
|
|
|
const option = computed<UiChartOption>(() => ({
|
|
animationDuration: 400,
|
|
color: slices.value.map((s) => s.color),
|
|
tooltip: {
|
|
trigger: 'item',
|
|
textStyle: { fontFamily: 'IRANSans, Tahoma, sans-serif' },
|
|
formatter: (p) => {
|
|
const item = Array.isArray(p) ? p[0] : p;
|
|
return `${item.name}<br/>${toFaDigits(Number(item.value ?? 0))} — ${toFaDigits(item.percent ?? 0)}٪`;
|
|
},
|
|
},
|
|
series: [
|
|
{
|
|
type: 'pie',
|
|
radius: ['58%', '82%'],
|
|
center: ['50%', '50%'],
|
|
silent: !hasData.value,
|
|
avoidLabelOverlap: true,
|
|
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
|
|
label: { show: false },
|
|
labelLine: { show: false },
|
|
data: hasData.value
|
|
? slices.value.map((s) => ({ name: s.name, value: s.value }))
|
|
: [{ name: '', value: 1, itemStyle: { color: '#e5e7eb' } }],
|
|
},
|
|
],
|
|
}));
|
|
</script>
|
|
|
|
<template>
|
|
<section class="bg-white rounded-2xl border border-slate-200/80 p-4 shadow-sm h-full flex flex-col">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h3 class="!m-0 !leading-6 font-bold text-slate-800 text-base">وضعیت معاملات</h3>
|
|
<span class="text-xs font-bold bg-emerald-50 text-emerald-700 border border-emerald-200/60 px-2.5 py-1 rounded-lg">
|
|
نرخ برد {{ toFaDigits(summary.winRate) }}٪
|
|
</span>
|
|
</div>
|
|
<div class="flex flex-col sm:flex-row items-center gap-4">
|
|
<div class="w-full sm:w-[48%] sm:max-w-[240px] shrink-0">
|
|
<div class="relative w-full aspect-square">
|
|
<UiChart :option="option" height="100%" />
|
|
<div class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
|
|
<span class="text-2xl font-black text-slate-800 leading-none">{{ toFaDigits(total) }}</span>
|
|
<span class="text-xs text-slate-400 mt-1">معامله کل</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full sm:flex-1 min-w-0 space-y-2.5">
|
|
<div class="p-3 rounded-xl bg-slate-50/80 border border-slate-100 flex items-center justify-between gap-2">
|
|
<div>
|
|
<div class="text-xs font-bold text-slate-700">باز</div>
|
|
<div class="text-[11px] text-slate-400">در جریان</div>
|
|
</div>
|
|
<div class="text-left">
|
|
<div class="text-sm font-black text-slate-800">{{ toFaDigits(summary.openCount) }}</div>
|
|
<div class="text-xs text-slate-500">{{ formatAmount(summary.openAmount) }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-3 rounded-xl bg-emerald-50/50 border border-emerald-100 flex items-center justify-between gap-2">
|
|
<div>
|
|
<div class="text-xs font-bold text-emerald-900">برنده (۱۲ ماه)</div>
|
|
<div class="text-[11px] text-emerald-600/80">تکمیل موفق</div>
|
|
</div>
|
|
<div class="text-left">
|
|
<div class="text-sm font-black text-emerald-700">{{ toFaDigits(summary.wonCount) }}</div>
|
|
<div class="text-xs text-emerald-600">{{ formatAmount(summary.wonAmount) }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="p-3 rounded-xl bg-rose-50/40 border border-rose-100 flex items-center justify-between gap-2">
|
|
<div>
|
|
<div class="text-xs font-bold text-rose-900">باخته (۱۲ ماه)</div>
|
|
<div class="text-[11px] text-rose-500">لغو یا عدم توافق</div>
|
|
</div>
|
|
<div class="text-left">
|
|
<div class="text-sm font-black text-rose-600">{{ toFaDigits(summary.lostCount) }}</div>
|
|
<div class="text-xs text-rose-500">{{ formatAmount(summary.lostAmount) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|