feat: add FunnelChart, RevenueChart, ObjectTypeChart, AvgTimeChart
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts';
|
||||
|
||||
const COLORS = ['#f59e0b', '#0ea5e9', '#8b5cf6', '#f97316'];
|
||||
|
||||
const AvgTimeChart = ({ data }) => (
|
||||
<div>
|
||||
<h3 style={{ fontFamily: 'var(--font-heading)', fontSize: '0.95rem', fontWeight: 700, marginBottom: '14px' }}>
|
||||
Среднее время в стадии (дни)
|
||||
</h3>
|
||||
{data.every((d) => d.avgDays === 0) ? (
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', textAlign: 'center', padding: '40px 0' }}>
|
||||
Недостаточно данных
|
||||
</p>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={210}>
|
||||
<BarChart data={data} margin={{ left: 10, right: 20 }}>
|
||||
<XAxis dataKey="status" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
||||
<YAxis tick={{ fontSize: 11, fill: '#94a3b8' }} unit=" д" />
|
||||
<Tooltip formatter={(v) => [`${v} дней`]} />
|
||||
<Bar dataKey="avgDays" radius={[4, 4, 0, 0]}>
|
||||
{data.map((entry, i) => <Cell key={entry.status} fill={COLORS[i % COLORS.length]} />)}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default AvgTimeChart;
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts';
|
||||
|
||||
const COLORS = ['#f59e0b', '#0ea5e9', '#8b5cf6', '#f97316', '#22c55e'];
|
||||
|
||||
const FunnelChart = ({ data }) => (
|
||||
<div>
|
||||
<h3 style={{ fontFamily: 'var(--font-heading)', fontSize: '0.95rem', fontWeight: 700, marginBottom: '14px' }}>
|
||||
Воронка по стадиям
|
||||
</h3>
|
||||
{data.every((d) => d.count === 0) ? (
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', textAlign: 'center', padding: '40px 0' }}>Нет данных</p>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={210}>
|
||||
<BarChart data={data} layout="vertical" margin={{ left: 10, right: 30 }}>
|
||||
<XAxis type="number" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
||||
<YAxis type="category" dataKey="status" tick={{ fontSize: 11, fill: '#94a3b8' }} width={95} />
|
||||
<Tooltip formatter={(v) => [`${v} заказов`]} />
|
||||
<Bar dataKey="count" radius={[0, 4, 4, 0]}>
|
||||
{data.map((entry, i) => <Cell key={entry.status} fill={COLORS[i]} />)}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default FunnelChart;
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
|
||||
const COLORS = ['#0ea5e9', '#f59e0b', '#8b5cf6', '#22c55e', '#f97316'];
|
||||
const TYPE_LABELS = {
|
||||
industrial: 'Промышленный', commercial: 'Коммерческий',
|
||||
infrastructure: 'Инфраструктура', residential: 'Частный', other: 'Другое',
|
||||
};
|
||||
|
||||
const ObjectTypeChart = ({ data }) => (
|
||||
<div>
|
||||
<h3 style={{ fontFamily: 'var(--font-heading)', fontSize: '0.95rem', fontWeight: 700, marginBottom: '14px' }}>
|
||||
Типы объектов
|
||||
</h3>
|
||||
{data.length === 0 ? (
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', textAlign: 'center', padding: '40px 0' }}>Нет данных</p>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={210}>
|
||||
<PieChart>
|
||||
<Pie data={data} dataKey="count" nameKey="type" cx="50%" cy="50%" innerRadius={50} outerRadius={80}>
|
||||
{data.map((entry, i) => <Cell key={entry.type} fill={COLORS[i % COLORS.length]} />)}
|
||||
</Pie>
|
||||
<Tooltip formatter={(v, name) => [v, TYPE_LABELS[name] || name]} />
|
||||
<Legend formatter={(v) => TYPE_LABELS[v] || v} iconSize={10} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ObjectTypeChart;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts';
|
||||
|
||||
const RevenueChart = ({ data }) => (
|
||||
<div>
|
||||
<h3 style={{ fontFamily: 'var(--font-heading)', fontSize: '0.95rem', fontWeight: 700, marginBottom: '14px' }}>
|
||||
Выручка по месяцам (₽)
|
||||
</h3>
|
||||
{data.length === 0 ? (
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', textAlign: 'center', padding: '40px 0' }}>Нет закрытых заказов</p>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={210}>
|
||||
<LineChart data={data} margin={{ left: 10, right: 20 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="rgba(15,23,42,0.05)" />
|
||||
<XAxis dataKey="month" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
||||
<YAxis tick={{ fontSize: 11, fill: '#94a3b8' }} tickFormatter={(v) => `${(v / 1000).toFixed(0)}к`} />
|
||||
<Tooltip formatter={(v) => [`${v.toLocaleString('ru-RU')} ₽`, 'Выручка']} />
|
||||
<Line type="monotone" dataKey="revenue" stroke="var(--accent-blue)" strokeWidth={2} dot={{ fill: 'var(--accent-blue)', r: 4 }} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default RevenueChart;
|
||||
Reference in New Issue
Block a user