This commit is contained in:
arys
2026-01-22 13:36:32 +05:00
parent 1ee10a64a5
commit 112f470cc3
+85 -78
View File
@@ -301,7 +301,7 @@
<div class="col-span-12">
<div class="rounded-2xl bg-white shadow-sm border border-slate-200/60 p-4">
<div class="flex items-center gap-1.5 mb-2">
<div class="text-[16px] font-semibold text-slate-800">Арыстанбек</div>
<div class="text-[16px] font-semibold text-slate-800">Возрастная структура</div>
<i
class="pi pi-question-circle text-[12px] text-slate-400 cursor-help"
v-tooltip.top="'Процентное распределение аудитории по возрастным категориям (от 18 до 55+). Помогает адаптировать рекламные сообщения под возрастные группы.'"
@@ -310,20 +310,6 @@
<div class="h-[240px]">
<Chart type="bar" :data="ageBarData" :options="compactBarOptions" />
</div>
<div class="mt-4 pt-4 border-t border-slate-200/70">
<div class="overflow-hidden rounded-xl border border-slate-200/70">
<div class="grid grid-cols-12 bg-slate-50 text-[12px] font-semibold text-slate-600">
<div class="col-span-8 px-3 py-2">Возраст</div>
<div class="col-span-4 px-3 py-2 text-right">Доля</div>
</div>
<div class="divide-y divide-slate-200/70">
<div v-for="row in ageRows" :key="row.key" class="grid grid-cols-12 px-3 py-2.5">
<div class="col-span-8 text-[13px] text-slate-800">{{ row.label }}</div>
<div class="col-span-4 text-[13px] text-slate-800 text-right">{{ row.value }}%</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-span-12">
@@ -338,20 +324,6 @@
<div class="h-[220px]">
<Chart type="bar" :data="incomeBarData" :options="compactBarOptions" />
</div>
<div class="mt-4 pt-4 border-t border-slate-200/70">
<div class="overflow-hidden rounded-xl border border-slate-200/70">
<div class="grid grid-cols-12 bg-slate-50 text-[12px] font-semibold text-slate-600">
<div class="col-span-8 px-3 py-2">Возраст</div>
<div class="col-span-4 px-3 py-2 text-right">Доход</div>
</div>
<div class="divide-y divide-slate-200/70">
<div v-for="row in incomeTableRows" :key="row.label" class="grid grid-cols-12 px-3 py-2.5">
<div class="col-span-8 text-[13px] text-slate-800">{{ row.label }}</div>
<div class="col-span-4 text-[13px] text-slate-800 text-right">{{ row.value }} </div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -414,20 +386,6 @@
<div class="h-[280px]">
<Chart type="bar" :data="visibilityBarData" :options="visibilityBarOptions" />
</div>
<div class="mt-4 pt-4 border-t border-slate-200/70">
<div class="overflow-hidden rounded-xl border border-slate-200/70">
<div class="grid grid-cols-12 bg-slate-50 text-[12px] font-semibold text-slate-600">
<div class="col-span-8 px-3 py-2">Конкурент</div>
<div class="col-span-4 px-3 py-2 text-right">Видимость</div>
</div>
<div class="divide-y divide-slate-200/70">
<div v-for="row in comparisonRows" :key="row.key" class="grid grid-cols-12 px-3 py-2.5">
<div class="col-span-8 text-[13px] text-slate-800">{{ row.label }}</div>
<div class="col-span-4 text-[13px] text-slate-800 text-right">{{ row.visibility ?? '—' }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-span-12 lg:col-span-5">
@@ -731,20 +689,6 @@
<div class="h-[280px]">
<Chart type="bar" :data="channelSharesBarData" :options="channelSharesBarOptions" />
</div>
<div class="mt-4 pt-4 border-t border-slate-200/70">
<div class="overflow-hidden rounded-xl border border-slate-200/70">
<div class="grid grid-cols-12 bg-slate-50 text-[12px] font-semibold text-slate-600">
<div class="col-span-8 px-3 py-2">Канал</div>
<div class="col-span-4 px-3 py-2 text-right">Доля</div>
</div>
<div class="divide-y divide-slate-200/70">
<div v-for="row in channelSharesTableRows" :key="row.label" class="grid grid-cols-12 px-3 py-2.5">
<div class="col-span-8 text-[13px] text-slate-800">{{ row.label }}</div>
<div class="col-span-4 text-[13px] text-slate-800 text-right">{{ row.value }}%</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-span-12 lg:col-span-5">
@@ -1418,10 +1362,63 @@ const ageRows = computed(() => {
const compactBarOptions = computed(() => ({
maintainAspectRatio: false,
plugins: { legend: { display: false } },
plugins: {
legend: { display: false },
barValues: {
id: 'barValues',
afterDatasetsDraw(chart) {
const { ctx, data } = chart;
ctx.save();
ctx.font = 'bold 12px Inter, system-ui, sans-serif';
ctx.textBaseline = 'middle';
data.datasets.forEach((dataset, i) => {
const meta = chart.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach((bar, index) => {
const value = dataset.data[index];
if (value !== null && value !== undefined && value !== 0) {
const unit = dataset.unit || '';
const label = value.toLocaleString() + unit;
const isHorizontal = chart.options.indexAxis === 'y';
if (isHorizontal) {
const barWidth = Math.abs(bar.x - bar.base);
if (barWidth > 40) {
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'right';
ctx.fillText(label, bar.x - 10, bar.y);
} else {
ctx.fillStyle = '#64748b';
ctx.textAlign = 'left';
ctx.fillText(label, bar.x + 10, bar.y);
}
} else {
const barHeight = Math.abs(bar.base - bar.y);
ctx.textAlign = 'center';
if (barHeight > 25) {
ctx.fillStyle = '#ffffff';
ctx.fillText(label, bar.x, bar.y + 14);
} else {
ctx.fillStyle = '#64748b';
ctx.fillText(label, bar.x, bar.y - 10);
}
}
}
});
}
});
ctx.restore();
}
}
},
scales: {
x: { ticks: { color: '#64748B' }, grid: { display: false, drawBorder: false } },
y: { ticks: { color: '#64748B' }, grid: { color: 'rgba(148,163,184,0.25)', drawBorder: false } }
x: { ticks: { color: '#64748B', font: { size: 11 } }, grid: { display: false, drawBorder: false } },
y: {
ticks: { color: '#64748B', font: { size: 10 } },
grid: { color: 'rgba(148,163,184,0.15)', drawBorder: false },
beginAtZero: true
}
}
}));
@@ -1507,6 +1504,7 @@ const ageBarData = computed(() => ({
datasets: [
{
data: ageRows.value.map((r) => r.value),
unit: '%',
borderRadius: 10,
backgroundColor: (context) => {
const { chart } = context;
@@ -1573,6 +1571,7 @@ const incomeBarData = computed(() => ({
datasets: [
{
data: incomeRows.value.map((r) => r.value),
unit: ' ₸',
borderRadius: 10,
backgroundColor: (context) => {
const { chart } = context;
@@ -1683,6 +1682,7 @@ const visibilityBarData = computed(() => {
datasets: [
{
data: values,
unit: '%',
borderRadius: 12,
borderSkipped: false,
backgroundColor: (context) => {
@@ -1697,15 +1697,17 @@ const visibilityBarData = computed(() => {
};
});
const visibilityBarOptions = computed(() => ({
indexAxis: 'y',
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { ticks: { color: '#64748B' }, grid: { color: 'rgba(148,163,184,0.25)', drawBorder: false } },
y: { ticks: { color: '#64748B' }, grid: { display: false, drawBorder: false } }
}
}));
const visibilityBarOptions = computed(() => {
const base = compactBarOptions.value;
return {
...base,
indexAxis: 'y',
scales: {
x: { ...base.scales.x, grid: { color: 'rgba(148,163,184,0.15)', drawBorder: false } },
y: { ...base.scales.y, grid: { display: false, drawBorder: false } }
}
};
});
const pptFunnelSteps = computed(() => {
const fm = report.value?.sections?.customer_journey?.funnel_metrics ?? {};
@@ -1825,6 +1827,7 @@ const decisionFactorsBarData = computed(() => {
datasets: [
{
data: values,
unit: '%',
borderRadius: 10,
backgroundColor: (context) => {
const { chart } = context;
@@ -1858,6 +1861,7 @@ const refusalReasonsBarData = computed(() => {
datasets: [
{
data: values,
unit: '%',
borderRadius: 10,
backgroundColor: (context) => {
const { chart } = context;
@@ -1893,6 +1897,7 @@ const channelSharesBarData = computed(() => {
datasets: [
{
data: items.map((i) => percentToNumber(i.value)),
unit: '%',
borderRadius: 12,
borderSkipped: false,
backgroundColor: (context) => {
@@ -1917,15 +1922,17 @@ const channelSharesTableRows = computed(() => {
];
});
const channelSharesBarOptions = computed(() => ({
indexAxis: 'y',
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { ticks: { color: '#64748B' }, grid: { color: 'rgba(148,163,184,0.25)', drawBorder: false } },
y: { ticks: { color: '#64748B' }, grid: { display: false, drawBorder: false } }
}
}));
const channelSharesBarOptions = computed(() => {
const base = compactBarOptions.value;
return {
...base,
indexAxis: 'y',
scales: {
x: { ...base.scales.x, grid: { color: 'rgba(148,163,184,0.15)', drawBorder: false } },
y: { ...base.scales.y, grid: { display: false, drawBorder: false } }
}
};
});
const channelEfficiencyRows = computed(() => {
const eff = report.value?.sections?.acquisition_channels?.channel_efficiency ?? {};