- Мобильная навигация: hamburger-меню и drawer вместо фиксированного sidebar - Модальные окна на весь экран при ширине < 480px - Адаптивные заголовки страниц и фильтры (touch-friendly) - Card view для таблицы операций при ширине < 600px - Горизонтальный скролл вкладок настроек - Увеличенные touch-targets (44px) для пагинации и кнопок - Уменьшенная высота графиков на мобильных - Поддержка safe-area-inset для устройств с вырезами - theme-color в index.html Made-with: Cursor
76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
import {
|
||
BarChart,
|
||
Bar,
|
||
XAxis,
|
||
YAxis,
|
||
CartesianGrid,
|
||
Tooltip,
|
||
Legend,
|
||
ResponsiveContainer,
|
||
} from 'recharts';
|
||
import type { TimeseriesItem } from '@family-budget/shared';
|
||
import { useMediaQuery } from '../hooks/useMediaQuery';
|
||
|
||
interface Props {
|
||
data: TimeseriesItem[];
|
||
}
|
||
|
||
const rubFormatter = new Intl.NumberFormat('ru-RU', {
|
||
style: 'currency',
|
||
currency: 'RUB',
|
||
maximumFractionDigits: 0,
|
||
});
|
||
|
||
export function TimeseriesChart({ data }: Props) {
|
||
const isMobile = useMediaQuery('(max-width: 600px)');
|
||
const chartHeight = isMobile ? 250 : 300;
|
||
|
||
if (data.length === 0) {
|
||
return <div className="chart-empty">Нет данных за период</div>;
|
||
}
|
||
|
||
const chartData = data.map((item) => ({
|
||
period: item.periodStart,
|
||
Расходы: Math.abs(item.expenseAmount) / 100,
|
||
Доходы: item.incomeAmount / 100,
|
||
}));
|
||
|
||
return (
|
||
<ResponsiveContainer width="100%" height={chartHeight}>
|
||
<BarChart data={chartData}>
|
||
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
|
||
<XAxis
|
||
dataKey="period"
|
||
tickFormatter={(v: string) => {
|
||
const d = new Date(v);
|
||
return `${d.getDate()}.${String(d.getMonth() + 1).padStart(2, '0')}`;
|
||
}}
|
||
fontSize={12}
|
||
stroke="#64748b"
|
||
/>
|
||
<YAxis
|
||
tickFormatter={(v: number) =>
|
||
v >= 1000 ? `${(v / 1000).toFixed(0)}к` : String(v)
|
||
}
|
||
fontSize={12}
|
||
stroke="#64748b"
|
||
/>
|
||
<Tooltip
|
||
formatter={(value: number) => rubFormatter.format(value)}
|
||
/>
|
||
<Legend />
|
||
<Bar
|
||
dataKey="Расходы"
|
||
fill="#ef4444"
|
||
radius={[4, 4, 0, 0]}
|
||
/>
|
||
<Bar
|
||
dataKey="Доходы"
|
||
fill="#10b981"
|
||
radius={[4, 4, 0, 0]}
|
||
/>
|
||
</BarChart>
|
||
</ResponsiveContainer>
|
||
);
|
||
}
|