From d42c56154cac523a560bc6d434f64a7024ea2376 Mon Sep 17 00:00:00 2001 From: barach6662001-bit Date: Thu, 23 Apr 2026 09:48:15 +0000 Subject: [PATCH] feat(sales): monthly revenue chart drill-down + URL-state for SalesList filters --- frontend/src/pages/Sales/RevenueAnalytics.tsx | 24 +++++++++++- frontend/src/pages/Sales/SalesList.tsx | 38 +++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/Sales/RevenueAnalytics.tsx b/frontend/src/pages/Sales/RevenueAnalytics.tsx index 23ba2bfb..6f00c2ed 100644 --- a/frontend/src/pages/Sales/RevenueAnalytics.tsx +++ b/frontend/src/pages/Sales/RevenueAnalytics.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import { formatUAH, formatNumber } from '../../utils/format'; import { Card, Col, Row, Select, Statistic, Empty, message, Tag } from 'antd'; import { DollarOutlined, ShoppingOutlined, UserOutlined, TrophyOutlined } from '@ant-design/icons'; @@ -29,6 +30,7 @@ const PIE_COLORS = chartPalette; export default function RevenueAnalytics() { const { t, lang } = useTranslation(); + const navigate = useNavigate(); const [year, setYear] = useState(new Date().getFullYear()); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -58,9 +60,24 @@ export default function RevenueAnalytics() { const monthlyChartData = (data?.byMonth ?? []).map((m) => ({ name: `${monthNames[m.month - 1]} ${m.year}`, + year: m.year, + month: m.month, [t.sales.revenueAmount]: m.totalAmount, })); + /** Drill-down from the monthly revenue chart → /sales pre-filtered to the + * clicked month. Uses the explicit `year`/`month` fields on each data + * point so we don't re-parse the localised label. */ + const handleMonthlyBarClick = (state: { activePayload?: Array<{ payload?: { year?: number; month?: number } }> } | null) => { + const p = state?.activePayload?.[0]?.payload; + if (!p?.year || !p?.month) return; + const mm = String(p.month).padStart(2, '0'); + const lastDay = new Date(Date.UTC(p.year, p.month, 0)).getUTCDate(); + const from = `${p.year}-${mm}-01`; + const to = `${p.year}-${mm}-${String(lastDay).padStart(2, '0')}`; + navigate(`/sales?from=${from}&to=${to}`); + }; + const productColumns = [ { title: t.sales.product, @@ -267,7 +284,12 @@ export default function RevenueAnalytics() { > {monthlyChartData.length > 0 ? ( - + !!v && /^\d{4}-\d{2}-\d{2}$/.test(v); + const urlFrom = searchParams.get('from'); + const urlTo = searchParams.get('to'); + const urlSearch = searchParams.get('search') || undefined; + const initialRange: [string, string] | null = + isIsoDate(urlFrom) && isIsoDate(urlTo) ? [urlFrom, urlTo] : null; + const [result, setResult] = useState | null>(null); const [fields, setFields] = useState([]); const [loading, setLoading] = useState(true); - const [search, setSearch] = useState(); - const [dateRange, setDateRange] = useState<[string, string] | null>(null); + const [search, setSearch] = useState(urlSearch); + const [dateRange, setDateRange] = useState<[string, string] | null>(initialRange); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); @@ -233,14 +246,31 @@ export default function SalesList() { { setSearch(e.target.value || undefined); setPage(1); }} + onChange={(e) => { + const v = e.target.value || undefined; + setSearch(v); + setPage(1); + setSearchParams((prev) => { + const p = new URLSearchParams(prev); + if (v) p.set('search', v); else p.delete('search'); + return p; + }, { replace: true }); + }} style={{ width: 240 }} allowClear /> { - setDateRange(dates[0] && dates[1] ? [dates[0], dates[1]] : null); + const next: [string, string] | null = dates[0] && dates[1] ? [dates[0], dates[1]] : null; + setDateRange(next); setPage(1); + setSearchParams((prev) => { + const p = new URLSearchParams(prev); + if (next) { p.set('from', next[0]); p.set('to', next[1]); } + else { p.delete('from'); p.delete('to'); } + return p; + }, { replace: true }); }} /> {canWrite && (