fix
This commit is contained in:
@@ -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;
|
||||
@@ -1521,7 +1519,7 @@ const ageBarData = computed(() => ({
|
||||
// Income by age rows
|
||||
const incomeRows = computed(() => {
|
||||
const incomeByAge = report.value?.sections?.target_audience?.income_by_age ?? {};
|
||||
|
||||
|
||||
// Маппинг ключей бэкенда на ключи фронтенда
|
||||
const backendKeyMap = {
|
||||
'18-24': { '18-24': 1.0 },
|
||||
@@ -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 ?? {};
|
||||
|
||||
Reference in New Issue
Block a user