fix the style and rerender the chart

This commit is contained in:
Saeed AB 2025-10-04 14:45:15 +03:30
parent a5ae3cc813
commit b0644786f1
2 changed files with 152 additions and 133 deletions

View File

@ -1,5 +1,5 @@
import { ChevronDown, ChevronUp, RefreshCw, Eye, Star, TrendingUp, Hexagon, Download } from "lucide-react"; import { ChevronDown, ChevronUp, RefreshCw, Eye, Star, TrendingUp, Hexagon, Download } from "lucide-react";
import { useCallback, useEffect, useRef, useState, useMemo } from "react"; import { useCallback, useEffect, useRef, useState, useMemo, memo } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -92,6 +92,135 @@ const columns: ColumnDef[] = [
{ key: "details", label: "جزئیات بیشتر", sortable: false, width: "120px" }, { key: "details", label: "جزئیات بیشتر", sortable: false, width: "120px" },
]; ];
// Memoized Vertical Bar Chart Component
const VerticalBarChart = memo<{
chartData: IdeaStatusData[];
loadingChart: boolean;
chartConfig: ChartConfig;
getChartStatusColor: (status: string) => string;
toPersianDigits: (input: string | number) => string;
formatNumber: (value: number) => string;
}>(({ chartData, loadingChart, chartConfig, getChartStatusColor, toPersianDigits, formatNumber }) => {
if (loadingChart) {
return (
<div className="p-6 space-y-4">
{/* Chart title skeleton */}
<div className="h-5 bg-gray-600 rounded animate-pulse w-32 mx-auto"></div>
{/* Chart area skeleton */}
<div className="relative h-48 bg-gradient-to-t from-gray-700/30 to-transparent rounded p-4">
{/* Y-axis labels */}
<div className="absolute left-2 top-4 space-y-6">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="h-3 bg-gray-600 rounded animate-pulse w-6"></div>
))}
</div>
{/* Bars skeleton */}
<div className="flex items-end justify-center gap-4 h-full pt-4 pb-8 ml-8">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex flex-col items-center gap-2">
{/* Bar */}
<div
className="bg-gray-600 rounded-t animate-pulse w-12"
style={{ height: `${Math.random() * 60 + 40}%` }}
></div>
{/* X-axis label */}
<div className="h-3 bg-gray-600 rounded animate-pulse w-16"></div>
</div>
))}
</div>
</div>
</div>
);
}
if (!chartData.length) {
return (
<div className="p-6 text-center">
<h3 className="text-lg font-persian font-semibold text-white mb-4">وضعیت ایده ها</h3>
<p className="text-gray-400 font-persian">هیچ دادهای یافت نشد</p>
</div>
);
}
// Prepare data for recharts
const rechartData = useMemo(() => chartData.map((item) => ({
status: item.idea_status,
count: item.idea_status_count,
fill: getChartStatusColor(item.idea_status),
})), [chartData, getChartStatusColor]);
return (
<ResponsiveContainer width="100%">
<ChartContainer config={chartConfig} className="w-full">
<BarChart
margin={{ top: 25, left: 12, right: 12, bottom: 12 }}
barGap={15}
barSize={45}
accessibilityLayer
data={rechartData}
>
<CartesianGrid vertical={false} stroke="#475569" />
<XAxis
dataKey="status"
axisLine={false}
tickLine={false}
tick={{
fill: '#fff',
fontSize: 14,
fontFamily: 'inherit'
}}
interval={0}
angle={0}
tickMargin={10}
textAnchor="middle"
/>
<YAxis
tickMargin={20}
axisLine={false}
tickLine={false}
tick={{
fill: '#9CA3AF',
fontSize: 12,
fontFamily: 'inherit'
}}
tickFormatter={(value) => toPersianDigits(value)}
label={{
value: "تعداد برنامه ها",
angle: -90,
position: "insideLeft",
fill: "#94a3b8",
fontSize: 11,
offset: 0,
dy: 0,
style: { textAnchor: "middle" },
}}
/>
<Bar
dataKey="count"
radius={[4, 4, 0, 0]}
>
<LabelList
dataKey="count"
position="top"
offset={12}
style={{
fill: "#ffffff",
fontSize: "16px",
fontWeight: "bold",
}}
formatter={(v: number) => `${formatNumber(Math.round(v))}`}
/>
</Bar>
</BarChart>
</ChartContainer>
</ResponsiveContainer>
);
});
const MemoizedVerticalBarChart = VerticalBarChart;
export function ManageIdeasTechPage() { export function ManageIdeasTechPage() {
const [ideas, setIdeas] = useState<IdeaData[]>([]); const [ideas, setIdeas] = useState<IdeaData[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -446,7 +575,7 @@ export function ManageIdeasTechPage() {
} }
}; };
const toPersianDigits = (input: string | number): string => { const toPersianDigits = useCallback((input: string | number): string => {
const str = String(input); const str = String(input);
const map: Record<string, string> = { const map: Record<string, string> = {
"0": "۰", "0": "۰",
@ -461,7 +590,7 @@ export function ManageIdeasTechPage() {
"9": "۹", "9": "۹",
}; };
return str.replace(/[0-9]/g, (d) => map[d] ?? d); return str.replace(/[0-9]/g, (d) => map[d] ?? d);
}; }, []);
const formatDate = (dateString: string | null) => { const formatDate = (dateString: string | null) => {
if (!dateString || dateString === "null" || dateString.trim() === "") { if (!dateString || dateString === "null" || dateString.trim() === "") {
@ -492,15 +621,15 @@ export function ManageIdeasTechPage() {
}; };
// Chart configuration for shadcn/ui // Chart configuration for shadcn/ui
const chartConfig: ChartConfig = { const chartConfig: ChartConfig = useMemo(() => ({
count: { count: {
label: "تعداد", label: "تعداد",
}, },
}; }), []);
// Color palette for idea status // Color palette for idea status
// Specific colors for idea statuses // Specific colors for idea statuses
const getChartStatusColor = (status: string) => { const getChartStatusColor = useCallback((status: string) => {
switch (status) { switch (status) {
case "اجرا شده": case "اجرا شده":
return "#69C8EA"; return "#69C8EA";
@ -513,7 +642,7 @@ export function ManageIdeasTechPage() {
default: default:
return "#6B7280"; return "#6B7280";
} }
}; }, []);
const statusColorPalette = ["#3AEA83", "#69C8EA", "#F76276", "#FFD700", "#A757FF", "#E884CE", "#C3BF8B", "#FB7185"]; const statusColorPalette = ["#3AEA83", "#69C8EA", "#F76276", "#FFD700", "#A757FF", "#E884CE", "#C3BF8B", "#FB7185"];
@ -601,124 +730,7 @@ export function ManageIdeasTechPage() {
} }
}; };
// Custom Vertical Bar Chart Component using shadcn/ui
const VerticalBarChart = () => {
if (loadingChart) {
return (
<div className="p-6 space-y-4">
{/* Chart title skeleton */}
<div className="h-5 bg-gray-600 rounded animate-pulse w-32 mx-auto"></div>
{/* Chart area skeleton */}
<div className="relative h-48 bg-gradient-to-t from-gray-700/30 to-transparent rounded p-4">
{/* Y-axis labels */}
<div className="absolute left-2 top-4 space-y-6">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="h-3 bg-gray-600 rounded animate-pulse w-6"></div>
))}
</div>
{/* Bars skeleton */}
<div className="flex items-end justify-center gap-4 h-full pt-4 pb-8 ml-8">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex flex-col items-center gap-2">
{/* Bar */}
<div
className="bg-gray-600 rounded-t animate-pulse w-12"
style={{ height: `${Math.random() * 60 + 40}%` }}
></div>
{/* X-axis label */}
<div className="h-3 bg-gray-600 rounded animate-pulse w-16"></div>
</div>
))}
</div>
</div>
</div>
);
}
if (!chartData.length) {
return (
<div className="p-6 text-center">
<h3 className="text-lg font-persian font-semibold text-white mb-4">وضعیت ایده ها</h3>
<p className="text-gray-400 font-persian">هیچ دادهای یافت نشد</p>
</div>
);
}
// Prepare data for recharts
const rechartData = chartData.map((item) => ({
status: item.idea_status,
count: item.idea_status_count,
fill: getChartStatusColor(item.idea_status),
}));
return (
<ResponsiveContainer width="100%">
<ChartContainer config={chartConfig} className="w-full">
<BarChart
margin={{ top : 25 ,left: 12, right: 12 }}
barGap={15}
barSize={45}
accessibilityLayer
data={rechartData} >
<CartesianGrid vertical={false} stroke="#475569" />
<XAxis
dataKey="status"
axisLine={false}
tickLine={false}
tick={{
fill: '#fff',
fontSize: 14,
fontFamily: 'inherit'
}}
interval={0}
angle={0}
tickMargin={10}
textAnchor="middle"
/>
<YAxis
tickMargin={20}
axisLine={false}
tickLine={false}
tick={{
fill: '#9CA3AF',
fontSize: 12,
fontFamily: 'inherit'
}}
tickFormatter={(value) => toPersianDigits(value)}
label={{
value: "تعداد برنامه ها" ,
angle: -90,
position: "insideLeft",
fill: "#94a3b8",
fontSize: 11,
offset: 0,
dy: 0,
style: { textAnchor: "middle" },
}}
/>
<Bar
dataKey="count"
radius={[4, 4, 0, 0]}
>
<LabelList
dataKey="count"
position="top"
offset={12}
style={{
fill: "#ffffff",
fontSize: "16px",
fontWeight: "bold",
}}
formatter={(v: number) => `${formatNumber(Math.round(v))}`}
/>
</Bar>
</BarChart>
</ChartContainer>
</ResponsiveContainer>
);
};
return ( return (
<DashboardLayout title="مدیریت ایده های فناوری و نوآوری"> <DashboardLayout title="مدیریت ایده های فناوری و نوآوری">
@ -727,7 +739,7 @@ style: { textAnchor: "middle" },
<div className="grid grid-cols-1 grid-rows-2 lg:grid-cols-3 gap-4 h-full"> <div className="grid grid-cols-1 grid-rows-2 lg:grid-cols-3 gap-4 h-full">
{/* People Ranking Table */} {/* People Ranking Table */}
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<h3 className="text-base text-center my-4 font-persian font-bold text-foreground"> <h3 className="text-base text-center mb-2 font-persian font-bold text-foreground">
رتبه بندی نوآوران رتبه بندی نوآوران
</h3> </h3>
<Card className="bg-transparent border-none rounded-xl overflow-hidden"> <Card className="bg-transparent border-none rounded-xl overflow-hidden">
@ -819,7 +831,7 @@ style: { textAnchor: "middle" },
{/* Main Ideas Table */} {/* Main Ideas Table */}
<div className="col-span-2 row-span-1"> <div className="col-span-2 row-span-1">
<h3 className="text-base text-center my-4 font-persian font-bold text-foreground"> <h3 className="text-base text-center mb-2 font-persian font-bold text-foreground">
لیست ایده ها لیست ایده ها
</h3> </h3>
<Card className="bg-transparent backdrop-blur-sm rounded-xl overflow-hidden"> <Card className="bg-transparent backdrop-blur-sm rounded-xl overflow-hidden">
@ -827,7 +839,7 @@ style: { textAnchor: "middle" },
<div className="relative"> <div className="relative">
<Table <Table
containerRef={scrollContainerRef} containerRef={scrollContainerRef}
containerClassName="overflow-auto custom-scrollbar max-h-[calc(50vh-180px)]" containerClassName="overflow-auto custom-scrollbar max-h-[calc(50vh-120px)]"
> >
<TableHeader className="sticky top-0 z-50 bg-pr-gray"> <TableHeader className="sticky top-0 z-50 bg-pr-gray">
<TableRow className="bg-pr-gray"> <TableRow className="bg-pr-gray">
@ -941,10 +953,17 @@ style: { textAnchor: "middle" },
</Card> </Card>
</div> </div>
{/* Chart Section */} {/* Chart Section */}
<BaseCard icon={TrendingUp} className="col-span-1 row-start-2 col-start-3 row-span-1" title="نمودار ایده‌ها"> <BaseCard icon={TrendingUp} className="col-span-1 mt-12 row-start-2 col-start-3 row-span-1" title="نمودار ایده‌ها">
<VerticalBarChart /> <MemoizedVerticalBarChart
chartData={chartData}
loadingChart={loadingChart}
chartConfig={chartConfig}
getChartStatusColor={getChartStatusColor}
toPersianDigits={toPersianDigits}
formatNumber={formatNumber}
/>
</BaseCard> </BaseCard>
<div className="col-span-1 col-start-2 row-start-2 flex flex-col-reverse justify-end gap-2 row-span-1"> <div className="col-span-1 col-start-2 mt-12 row-start-2 flex flex-col-reverse justify-end gap-2 row-span-1">
<BaseCard title="ایده‌های فناوری و نوآوری"> <BaseCard title="ایده‌های فناوری و نوآوری">
{loadingStats ? ( {loadingStats ? (
<div className="flex items-center gap-2 justify-center flex-row-reverse"> <div className="flex items-center gap-2 justify-center flex-row-reverse">

View File

@ -672,9 +672,9 @@ export function ProductInnovationPage() {
return ( return (
<DashboardLayout title="نوآوری در محصول"> <DashboardLayout title="نوآوری در محصول">
<div className="space-y-4 flex justify-center gap-4"> <div className=" flex w-full gap-4">
{/* Stats Cards */} {/* Stats Cards */}
<div className="flex flex-col gap-6"> <div className="flex flex-col flex-1 gap-6">
<div className="space-y-6 w-full"> <div className="space-y-6 w-full">
{/* Stats Grid */} {/* Stats Grid */}
<div className="grid grid-cols-2 grid-rows-2 gap-5 h-full"> <div className="grid grid-cols-2 grid-rows-2 gap-5 h-full">
@ -793,7 +793,7 @@ export function ProductInnovationPage() {
</div> </div>
{/* Data Table */} {/* Data Table */}
<Card className="bg-transparent rounded-2xl overflow-hidden"> <Card className="bg-transparent flex-2 rounded-2xl overflow-hidden">
<CardContent className="p-0"> <CardContent className="p-0">
<div className="relative"> <div className="relative">
<Table containerClassName="overflow-auto custom-scrollbar backdrop max-h-[calc(100vh-200px)]"> <Table containerClassName="overflow-auto custom-scrollbar backdrop max-h-[calc(100vh-200px)]">