fix the style and rerender the chart
This commit is contained in:
parent
a5ae3cc813
commit
b0644786f1
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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)]">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user