import { useState, useEffect, useRef, useLayoutEffect, useMemo, lazy, Suspense, } from 'react'; import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts'; import { TrendingUp, Package, IndianRupee } from 'lucide-react'; import './TopSellingProductsChart.scss'; import { useDispatch } from 'react-redux'; import { getSession } from '../../Services/Others'; import { changeBreadCrumb } from '../../Features/AppPage/CenterPage'; import dayjs from 'dayjs'; const DropDowns = lazy(() => import('../../Components/Forms/DropDown').then((module) => ({ default: module.DropDowns, })) ); const subDirectory = import.meta.env.BASE_URL; const items = [ { name: 'Home', link: `${subDirectory}app-page/home`, }, ]; const TopSellingProducts = ({ dates = [null, null], products = [], loading = false, totalPages, page = 1, setPage = () => {}, fetchProducts = () => {}, initialDate = true, }) => { const today = new Date(); const year = today.getFullYear(); const month = today.getMonth(); const formatDate = (date) => `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; const intialFromDate = `${year}-${String(month + 1).padStart(2, '0')}-01`; const intialToDate = formatDate(today); const dispatch = useDispatch(); const AppId = getSession('AppId'); const CompId = getSession('CompId'); const BranchId = getSession('BranchId'); const [chartType, setChartType] = useState('bar'); const [metric, setMetric] = useState('sales'); const chartWrapperRef = useRef(null); const [containerWidth, setContainerWidth] = useState(800); // fallback const [windowWidth, setWindowWidth] = useState(window.innerWidth); const [hoveredItem, setHoveredItem] = useState(null); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); console.log(products?.[0]?.TotalCount, 'products'); const options = useMemo(() => { const total = products?.[0]?.TotalCount || 0; const perPage = 10; const totalPages = Math.ceil(total / perPage); const arr = []; for (let page = 1; page <= totalPages; page++) { const start = (page - 1) * perPage + 1; const end = Math.min(page * perPage, total); arr.push({ label: `${start}-${end}`, value: page, }); } return arr; }, [products?.[0]?.TotalCount]); useLayoutEffect(() => { if (!chartWrapperRef.current) return; const el = chartWrapperRef.current; // set initial width setContainerWidth(el.clientWidth || 800); // ResizeObserver to update container width precisely const ro = new ResizeObserver((entries) => { for (const entry of entries) { const w = Math.floor(entry.contentRect.width); if (w && w !== containerWidth) setContainerWidth(w); } }); ro.observe(el); return () => ro.disconnect(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [chartWrapperRef.current]); // breakpoints const mobileBreakpoint = 430; // you said < 430px is problematic const isNarrow = containerWidth <= mobileBreakpoint; // compute sizes based on actual container width // make pie fill a good portion of container (leave some padding) const pieOuterRadius = Math.max( 48, Math.floor(containerWidth * (isNarrow ? 0.36 : 0.28)) ); const pieInnerRadius = Math.floor(pieOuterRadius * (isNarrow ? 0.5 : 0.55)); // height for ResponsiveContainer: tether to containerWidth so chart stays proportional const responsiveHeight = Math.max( 220, Math.floor(containerWidth * (isNarrow ? 0.62 : 0.5)) ); // tweak minAngle: small screens should allow smaller minAngle so slices remain visible const minAngle = isNarrow ? 4 : 8; console.log(pieOuterRadius, 'pieOuterRadius'); // const COLORS = ['#2563eb', '#7c3aed', '#db2777', '#ea580c', '#16a34a', '#0891b2', '#d97706', '#65a30d', '#4f46e5', '#0d9488']; const COLORS = [ '#4f8df5', // lighter blue (was #2563eb) '#9a6df3', // lighter violet (was #7c3aed) '#e95b96', // lighter pink (was #db2777) '#f48b3c', // lighter orange (was #ea580c) '#34c86a', // lighter green (was #16a34a) '#33bcd4', // lighter cyan (was #0891b2) '#e39a1a', // lighter amber (was #d97706) '#8bc329', // lighter lime (was #65a30d) '#6d63ef', // lighter indigo (was #4f46e5) '#35bfa9', // lighter teal (was #0d9488) ]; useEffect(() => { try { dispatch(changeBreadCrumb({ items: items })); } catch (error) { console.log(error?.message, 'error displaying breadcrumbs'); } }, []); const CustomTooltip = ({ active, payload }) => { if (active && payload && payload?.[0]) { const product = payload?.[0]?.payload; return (

{product?.ProdName} - {product?.UOMName}

{product?.BrandName ? (

Brand: {product?.BrandName}

) : null}

Variants:

{product?.variantDetails?.map((variant, idx) => (
{variant?.ProdVariantName}: {variant?.SalesCount} sold | ₹{variant?.Revenue}
))}
Total Sales: {product?.TotalSoldQty}
Total Revenue: ₹{product?.TotalRevenue}
); } return null; }; const renderPercentInside = ({ cx, cy, midAngle, innerRadius, outerRadius, percent, value, }) => { const percentValue = Math.round(percent * 100); // hide labels for very small percentages on narrow screens if (percentValue < (isNarrow ? 3 : 2)) return null; const RADIAN = Math.PI / 180; // place label inside slice (closer to inner radius on narrow screens) const radius = innerRadius + (outerRadius - innerRadius) * (isNarrow ? 0.55 : 0.6); const x = cx + radius * Math.cos(-midAngle * RADIAN); const y = cy + radius * Math.sin(-midAngle * RADIAN); const fontSize = isNarrow ? 10 : 12; // choose contrasting color: white usually works on colored slices return ( {/* {`${percentValue}%`} */} {value} ); }; const getBenchmarkValue = (maxValue) => { if (maxValue <= 0) return 100; const benchmarks = [ // Fine granularity for small numbers, coarser for large numbers 1, 2, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 225, 250, 275, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1250, 1500, 1750, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 6000, 7000, 8000, 9000, 10000, 12500, 15000, 17500, 20000, 25000, 30000, 35000, 40000, 45000, 50000, 60000, 70000, 80000, 90000, 100000, 125000, 150000, 175000, 200000, 250000, 300000, 350000, 400000, 450000, 500000, 600000, 700000, 800000, 900000, 1000000, ]; const benchmark = benchmarks.find((b) => b >= maxValue); return benchmark || Math.ceil(maxValue / 10000) * 10000; }; const chartData = products?.map((p) => ({ ...p, name: p?.ProdName, value: metric === 'sales' ? p?.TotalSoldQty : p?.TotalRevenue, benchMarkValue: metric === 'sales' ? p?.TotalRevenueCount : p?.TotalRevenueAmount, })); const benchmarkValue = getBenchmarkValue( Math.max(...(chartData?.map((d) => d?.benchMarkValue) || [0])) ); return (
{/* Stats Cards */}

Total Products

{products?.length}

Total Sales

{products?.reduce( (sum, p) => sum + (p?.TotalSoldQty || 0), 0 )}

Total Revenue

₹ {products ?.reduce((sum, p) => sum + (p?.TotalRevenue || 0), 0) ?.toFixed(2)}

{/* Controls */}
setMetric('sales')} className={`btn-metric ${metric === 'sales' ? 'active' : ''}`} > Sales Count
setMetric('revenue')} className={`btn-metric ${metric === 'revenue' ? 'active' : ''}`} > Revenue
setChartType('bar')} className={`btn-chart-type ${chartType === 'bar' ? 'active' : ''}`} > Bar Chart
setChartType('pie')} className={`btn-chart-type ${chartType === 'pie' ? 'active' : ''}`} > Pie Chart
Loading...
}> { setPage(value); const FromDate = dates?.[0]?.format('YYYY-MM-DD') || intialFromDate; const ToDate = dates?.[1]?.format('YYYY-MM-DD') || intialToDate; await fetchProducts({ AppId, CompId, BranchId, FromDate, ToDate, pageNumber: value, }); }} isOnchanges={page ? true : false} />
{(initialDate ? true : !dates && !dates?.[0] && !dates?.[1]) && intialFromDate && intialToDate && (
{`${dayjs(intialFromDate)?.format('DD/MMM/YYYY')} - ${dayjs(intialToDate)?.format('DD/MMM/YYYY')}`}
)}
{/* Chart */}
{loading && products?.length === 0 ? (
) : chartData?.length === 0 ? (
No data available
) : ( <> {chartType === 'bar' ? (
{[ 0, Math.round(benchmarkValue * 0.25), Math.round(benchmarkValue * 0.5), Math.round(benchmarkValue * 0.75), benchmarkValue, ].map((num, i) => (
{num}
))}
{chartData?.length > 0 ? ( <> {chartData?.map((item, index) => (
{ setHoveredItem(item); setMousePosition({ x: e.clientX, y: e.clientY, }); }} // style={{}} onMouseLeave={() => setHoveredItem(null)} onMouseMove={(e) => setMousePosition({ x: e.clientX, y: e.clientY }) } >
{`#${index + 1 + (page - 1) * 10}`} {item?.name}
{/* {((item?.value / Math.max(...chartData.map(d => d?.value))) * 100).toFixed(1)}% */}
{item?.value}
))} ) : (
No Data
0
)}
) : ( /* Replace your existing with this */ /* PIE + RIGHT-LEGEND: replace the existing pie rendering */
{chartData.map((entry, index) => ( ))} } />
{(() => { const total = chartData.reduce( (s, d) => s + Number(d?.value || 0), 0 ) || 1; return chartData.map((d, i) => { const color = COLORS[i % COLORS.length]; const value = Number(d?.value || 0); const percent = total ? (value / total) * 100 : 0; return (
{d?.name}
{value} {percent.toFixed(2)}%
); }); })()}
)} )}
{hoveredItem && (
)} ); }; export default TopSellingProducts;