diff --git a/src/views/pages/MarketingAnalysis.vue b/src/views/pages/MarketingAnalysis.vue
index a586411..542fe95 100644
--- a/src/views/pages/MarketingAnalysis.vue
+++ b/src/views/pages/MarketingAnalysis.vue
@@ -301,7 +301,7 @@
-
Арыстанбек
+
Возрастная структура
-
-
-
-
-
-
{{ row.label }}
-
{{ row.value }}%
-
-
-
-
@@ -338,20 +324,6 @@
-
-
-
-
-
-
{{ row.label }}
-
{{ row.value }} ₸
-
-
-
-
@@ -414,20 +386,6 @@
-
-
-
-
Конкурент
-
Видимость
-
-
-
-
{{ row.label }}
-
{{ row.visibility ?? '—' }}
-
-
-
-
@@ -731,20 +689,6 @@
-
-
-
-
-
-
{{ row.label }}
-
{{ row.value }}%
-
-
-
-
@@ -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 ?? {};