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 { useCallback, useEffect, useRef, useState, useMemo } from "react";
|
||||
import { useCallback, useEffect, useRef, useState, useMemo, memo } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
|
|
@ -92,6 +92,135 @@ const columns: ColumnDef[] = [
|
|||
{ 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() {
|
||||
const [ideas, setIdeas] = useState<IdeaData[]>([]);
|
||||
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 map: Record<string, string> = {
|
||||
"0": "۰",
|
||||
|
|
@ -461,7 +590,7 @@ export function ManageIdeasTechPage() {
|
|||
"9": "۹",
|
||||
};
|
||||
return str.replace(/[0-9]/g, (d) => map[d] ?? d);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const formatDate = (dateString: string | null) => {
|
||||
if (!dateString || dateString === "null" || dateString.trim() === "") {
|
||||
|
|
@ -492,15 +621,15 @@ export function ManageIdeasTechPage() {
|
|||
};
|
||||
|
||||
// Chart configuration for shadcn/ui
|
||||
const chartConfig: ChartConfig = {
|
||||
const chartConfig: ChartConfig = useMemo(() => ({
|
||||
count: {
|
||||
label: "تعداد",
|
||||
},
|
||||
};
|
||||
}), []);
|
||||
|
||||
// Color palette for idea status
|
||||
// Specific colors for idea statuses
|
||||
const getChartStatusColor = (status: string) => {
|
||||
const getChartStatusColor = useCallback((status: string) => {
|
||||
switch (status) {
|
||||
case "اجرا شده":
|
||||
return "#69C8EA";
|
||||
|
|
@ -513,7 +642,7 @@ export function ManageIdeasTechPage() {
|
|||
default:
|
||||
return "#6B7280";
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
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 (
|
||||
<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">
|
||||
{/* People Ranking Table */}
|
||||
<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>
|
||||
<Card className="bg-transparent border-none rounded-xl overflow-hidden">
|
||||
|
|
@ -819,7 +831,7 @@ style: { textAnchor: "middle" },
|
|||
|
||||
{/* Main Ideas Table */}
|
||||
<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>
|
||||
<Card className="bg-transparent backdrop-blur-sm rounded-xl overflow-hidden">
|
||||
|
|
@ -827,7 +839,7 @@ style: { textAnchor: "middle" },
|
|||
<div className="relative">
|
||||
<Table
|
||||
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">
|
||||
<TableRow className="bg-pr-gray">
|
||||
|
|
@ -941,10 +953,17 @@ style: { textAnchor: "middle" },
|
|||
</Card>
|
||||
</div>
|
||||
{/* Chart Section */}
|
||||
<BaseCard icon={TrendingUp} className="col-span-1 row-start-2 col-start-3 row-span-1" title="نمودار ایدهها">
|
||||
<VerticalBarChart />
|
||||
<BaseCard icon={TrendingUp} className="col-span-1 mt-12 row-start-2 col-start-3 row-span-1" title="نمودار ایدهها">
|
||||
<MemoizedVerticalBarChart
|
||||
chartData={chartData}
|
||||
loadingChart={loadingChart}
|
||||
chartConfig={chartConfig}
|
||||
getChartStatusColor={getChartStatusColor}
|
||||
toPersianDigits={toPersianDigits}
|
||||
formatNumber={formatNumber}
|
||||
/>
|
||||
</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="ایدههای فناوری و نوآوری">
|
||||
{loadingStats ? (
|
||||
<div className="flex items-center gap-2 justify-center flex-row-reverse">
|
||||
|
|
|
|||
|
|
@ -672,9 +672,9 @@ export function ProductInnovationPage() {
|
|||
|
||||
return (
|
||||
<DashboardLayout title="نوآوری در محصول">
|
||||
<div className="space-y-4 flex justify-center gap-4">
|
||||
<div className=" flex w-full gap-4">
|
||||
{/* 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">
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-5 h-full">
|
||||
|
|
@ -793,7 +793,7 @@ export function ProductInnovationPage() {
|
|||
</div>
|
||||
|
||||
{/* Data Table */}
|
||||
<Card className="bg-transparent rounded-2xl overflow-hidden">
|
||||
<Card className="bg-transparent flex-2 rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-0">
|
||||
<div className="relative">
|
||||
<Table containerClassName="overflow-auto custom-scrollbar backdrop max-h-[calc(100vh-200px)]">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user