.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
## Документация для фронтенда: как отображать графики из маркетингового анализа
|
||||
|
||||
Сервис возвращает данные для графиков в двух местах:
|
||||
|
||||
- **`report.chartsData`** (объект-словарь ключ → данные графика/таблицы)
|
||||
- **`report.fullAnalysis`** (markdown-текст), где данные вставлены **инлайн** как fenced-блоки:
|
||||
- формат: ```json:<key> … ```
|
||||
|
||||
Фронтенд может рендерить графики либо **по `chartsData` (проще)**, либо **инлайн** — парся `fullAnalysis` и заменяя ` ```json:<key>` на React-компоненты.
|
||||
|
||||
---
|
||||
|
||||
### Ключи `json:<key>`, которые нужно поддержать
|
||||
|
||||
#### Chart.js config (рендерить через Chart.js / react-chartjs-2)
|
||||
- **`seasonality`**: сезонность (Chart.js config)
|
||||
- **`audienceAge`**: возрастное распределение (Chart.js config)
|
||||
- **`audienceGender`**: гендерное распределение (Chart.js config)
|
||||
- **`marketShareChart`**: доли рынка (Chart.js config)
|
||||
- **`channelsPotential`**: потенциал каналов (Chart.js config)
|
||||
- **`funnel`**: воронка (Chart.js config)
|
||||
Примечание: в `chartsData` ключ может быть `conversionFunnel`, но в тексте `fullAnalysis` блок идёт как `json:funnel`.
|
||||
|
||||
#### Таблица (рендерить табличным компонентом)
|
||||
- **`comparisonTable`**: сравнительная таблица конкурентов (обычно `Array<Object>`)
|
||||
|
||||
---
|
||||
|
||||
### Формат данных для графиков (Chart.js config)
|
||||
|
||||
Ваш пример — это **валидный Chart.js config** (минимально нужные поля: `labels`, `datasets[]`):
|
||||
|
||||
```json
|
||||
{
|
||||
"datasets": [
|
||||
{
|
||||
"data": [25, 30, 20, 25],
|
||||
"label": "Возрастные группы"
|
||||
}
|
||||
],
|
||||
"labels": ["18-24", "25-34", "35-44", "45+"]
|
||||
}
|
||||
```
|
||||
|
||||
Рендеринг (пример для Bar):
|
||||
```ts
|
||||
<Bar data={chartJson} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Инлайн-рендеринг из `fullAnalysis` (React + react-markdown)
|
||||
|
||||
Идея: перехватить `code`-блоки, найти `json:<key>`, распарсить JSON и заменить на компонент.
|
||||
|
||||
```tsx
|
||||
import React from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
function parseInfoString(className?: string) {
|
||||
// react-markdown обычно кладёт info string как className вида:
|
||||
// "language-json:audienceAge" или "language-json:marketShareChart"
|
||||
const m = /language-([^ ]+)/.exec(className || "");
|
||||
if (!m) return null;
|
||||
const raw = m[1]; // "json:audienceAge"
|
||||
const idx = raw.indexOf(":");
|
||||
if (idx === -1) return { lang: raw, key: null };
|
||||
return { lang: raw.slice(0, idx), key: raw.slice(idx + 1) };
|
||||
}
|
||||
|
||||
export function ReportMarkdown({ markdown }: { markdown: string }) {
|
||||
return (
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
code({ inline, className, children, ...props }) {
|
||||
if (inline) return <code className={className} {...props}>{children}</code>;
|
||||
|
||||
const info = parseInfoString(className);
|
||||
if (!info || info.lang !== "json" || !info.key) {
|
||||
return <pre><code className={className} {...props}>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const raw = String(children).replace(/\n$/, "");
|
||||
let data: any;
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch {
|
||||
return <pre><code className={className} {...props}>{children}</code></pre>;
|
||||
}
|
||||
|
||||
switch (info.key) {
|
||||
case "seasonality":
|
||||
return <SeasonalityChart data={data} />;
|
||||
case "audienceAge":
|
||||
return <AudienceAgeChart data={data} />;
|
||||
case "audienceGender":
|
||||
return <AudienceGenderChart data={data} />;
|
||||
case "marketShareChart":
|
||||
return <MarketShareChart data={data} />;
|
||||
case "channelsPotential":
|
||||
return <ChannelsPotentialChart data={data} />;
|
||||
case "funnel":
|
||||
return <FunnelChart data={data} />;
|
||||
case "comparisonTable":
|
||||
return <ComparisonTable data={data} />;
|
||||
default:
|
||||
return <pre><code className={className} {...props}>{children}</code></pre>;
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
{markdown}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Рекомендации по устойчивости
|
||||
|
||||
- **JSON.parse**: всегда `trim`/убирайте trailing newline (`replace(/\n$/, "")`).
|
||||
- **Fallback**: для неизвестных ключей оставляйте `<pre>` (чтобы ничего не “ломалось”).
|
||||
- **Таблицы**: `comparisonTable` лучше рендерить как таблицу, а не Chart.js.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user