Refactor process impacts chart to use new CustomBarChart component (#3)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Saeed 2025-08-13 18:15:02 +03:30 committed by GitHub
parent e27774c45a
commit 699548c674
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 178 additions and 102 deletions

View File

@ -4,6 +4,8 @@ import { Card, CardContent } from "~/components/ui/card";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Checkbox } from "~/components/ui/checkbox"; import { Checkbox } from "~/components/ui/checkbox";
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
import type { BarChartData } from "~/components/ui/custom-bar-chart";
import { import {
Table, Table,
TableBody, TableBody,
@ -583,108 +585,38 @@ export function ProcessInnovationPage() {
{/* Process Impacts Chart */} {/* Process Impacts Chart */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden"> <Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden">
<CardContent className="p-4"> <CardContent className="p-4">
<div className="mb-4"> <CustomBarChart
<h3 className="text-xl font-bold text-white font-persian text-right mb-2"> title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
تاثیرات فرآیندی به صورت درصد مقایسه ای loading={statsLoading}
</h3> data={[
</div> {
label: "کاهش توقفات تولید",
{/* Chart Container */} value: stats.percentProductionStops || 0,
<div className="space-y-6 flex flex-col"> color: "bg-emerald-400",
{/* Chart Item 1 */} labelColor: "text-white"
<div className="flex items-center gap-3"> },
<span className="text-white font-persian text-sm min-w-[140px] text-right">کاهش توقفات تولید</span> {
<div className="flex-1 flex items-center bg-gray-700 rounded-full h-5 relative"> label: "رفع گلوگاه تولید",
<div value: stats.percentBottleneckRemoval || 0,
className="bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out" color: "bg-emerald-400",
style={{ labelColor: "text-white"
width: `${Math.min((stats.percentProductionStops || 0), 100)}%` },
}} {
> label: "کاهش ارز بری",
</div> value: stats.percentCurrencyReduction || 0,
</div> color: "bg-emerald-400",
<span className="text-emerald-400 font-bold text-sm min-w-[40px] text-left"> labelColor: "text-white"
{formatNumber(((stats.percentProductionStops ?? 0) as number).toFixed?.(1) ?? (stats.percentProductionStops ?? 0))}% },
</span> {
</div> label: "کاهش خرابی پر تکرار",
value: stats.percentFailuresReduction || 0,
{/* Chart Item 2 */} color: "bg-emerald-400",
<div className="flex items-center gap-3"> labelColor: "text-white"
<span className="text-white font-persian text-sm min-w-[140px] text-right">رفع گلوگاه تولید</span> }
<div className="flex-1 flex items-center bg-gray-700 rounded-full h-5 relative"> ]}
<div barHeight="h-5"
className="bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out" showAxisLabels={true}
style={{ />
width: `${Math.min((stats.percentBottleneckRemoval || 0), 100)}%`
}}
>
</div>
</div>
<span className="text-emerald-400 font-bold text-sm min-w-[40px] text-left">
{formatNumber(((stats.percentBottleneckRemoval ?? 0) as number).toFixed?.(1) ?? (stats.percentBottleneckRemoval ?? 0))}%
</span>
</div>
{/* Chart Item 3 */}
<div className="flex items-center gap-3">
<span className="text-white font-persian text-sm min-w-[140px] text-right">کاهش ارز بری</span>
<div className="flex-1 flex items-center bg-gray-700 rounded-full h-5 relative">
<div
className="bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out"
style={{
width: `${Math.min((stats.percentCurrencyReduction || 0), 100)}%`
}}
>
</div>
</div>
<span className="text-emerald-400 font-bold text-sm min-w-[40px] text-left">
{formatNumber(((stats.percentCurrencyReduction ?? 0) as number).toFixed?.(0) ?? (stats.percentCurrencyReduction ?? 0))}%
</span>
</div>
{/* Chart Item 4 */}
<div className="flex items-center gap-3">
<span className="text-white font-persian text-sm min-w-[140px] text-right">کاهش خرابی پر تکرار</span>
<div className="flex-1 flex items-center bg-gray-700 rounded-full h-5 relative">
<div
className="bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out"
style={{
width: `${Math.min((stats.percentFailuresReduction || 0), 100)}%`
}}
>
</div>
</div>
<span className="text-emerald-400 font-bold text-sm min-w-[40px] text-left">
{formatNumber(((stats.percentFailuresReduction ?? 0) as number).toFixed?.(1) ?? (stats.percentFailuresReduction ?? 0))}%
</span>
</div>
<div className="flex items-center ml-[50px] gap-3">
<span className="min-w-[140px]"></span>
<div className="flex w-full justify-between pt-4 border-t border-gray-700">
<span className="text-gray-400 text-xs">۰٪</span>
{
(() => {
const p1 = (stats.percentProductionStops || 0);
const p2 = (stats.percentBottleneckRemoval || 0);
const p3 = (stats.percentCurrencyReduction || 0);
const p4 = (stats.percentFailuresReduction || 0);
const maxVal = Math.max(p1, p2, p3, p4);
return (
<>
<span className="text-gray-400 text-xs">{formatNumber(Math.round(maxVal / 4))}٪</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round(maxVal / 2))}٪</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round((maxVal * 3) / 4))}٪</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round(maxVal))}٪</span>
</>
);
})()
}
</div>
</div>
</div>
{/* Percentage Scale */}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View File

@ -0,0 +1,144 @@
import * as React from "react";
export interface BarChartData {
label: string;
value: number;
maxValue?: number;
color?: string;
labelColor?: string;
valuePrefix?: string;
valueSuffix?: string;
}
interface CustomBarChartProps {
data: BarChartData[];
title?: string;
height?: string;
barHeight?: string;
showAxisLabels?: boolean;
className?: string;
loading?: boolean;
}
export function CustomBarChart({
data,
title,
height = "auto",
barHeight = "h-6",
showAxisLabels = true,
className = "",
loading = false
}: CustomBarChartProps) {
// Calculate the maximum value across all data points for consistent scaling
const globalMaxValue = Math.max(...data.map(item => item.maxValue || item.value));
// Function to format numbers with Persian digits
const formatNumber = (value: number): string => {
const str = String(value);
const map: Record<string, string> = {
"0": "۰", "1": "۱", "2": "۲", "3": "۳", "4": "۴",
"5": "۵", "6": "۶", "7": "۷", "8": "۸", "9": "۹"
};
return str.replace(/[0-9]/g, (d) => map[d] ?? d);
};
// Loading skeleton
if (loading) {
return (
<div className={`space-y-6 ${className}`} style={{ height }}>
{title && (
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
)}
<div className="space-y-4">
{Array.from({ length: 4 }).map((_, index) => (
<div key={index} className="flex items-center gap-3">
{/* Label skeleton */}
<div className="h-4 bg-gray-600 rounded animate-pulse min-w-[160px]"></div>
{/* Bar skeleton */}
<div className="flex-1 bg-gray-700 rounded-full h-6">
<div className="h-6 bg-gray-600 rounded-full animate-pulse" style={{ width: `${Math.random() * 60 + 20}%` }}></div>
</div>
{/* Value skeleton */}
<div className="h-4 bg-gray-600 rounded animate-pulse min-w-[60px]"></div>
</div>
))}
</div>
</div>
);
}
return (
<div className={`space-y-6 ${className}`} style={{ height }}>
{title && (
<h3 className="text-xl font-bold text-white font-persian text-right mb-4">
{title}
</h3>
)}
<div className="space-y-4">
{data.map((item, index) => {
const percentage = globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
const displayValue = item.value.toFixed(1);
return (
<div key={index} className="flex items-center gap-3">
{/* Label */}
<span
className={`font-persian text-sm min-w-[160px] text-right ${
item.labelColor || 'text-white'
}`}
>
{item.label}
</span>
{/* Bar Container */}
<div className={`flex-1 flex items-center bg-gray-700 rounded-full relative overflow-hidden ${barHeight}`}>
<div
className={`${barHeight} rounded-full transition-all duration-700 ease-out relative ${
item.color || 'bg-emerald-400'
}`}
style={{
width: `${Math.min(percentage, 100)}%`
}}
>
{/* Add a subtle gradient effect for better visual appeal */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent to-white/10 rounded-full"></div>
</div>
</div>
{/* Value Label */}
<span className={`font-bold text-sm min-w-[60px] text-left ${
item.color?.includes('emerald') ? 'text-emerald-400' :
item.color?.includes('blue') ? 'text-blue-400' :
item.color?.includes('purple') ? 'text-purple-400' :
item.color?.includes('red') ? 'text-red-400' :
item.color?.includes('yellow') ? 'text-yellow-400' :
'text-emerald-400'
}`}>
{item.valuePrefix || ''}{formatNumber(parseFloat(displayValue))}{item.valueSuffix || '%'}
</span>
</div>
);
})}
{/* Axis Labels */}
{showAxisLabels && globalMaxValue > 0 && (
<div className="flex items-center gap-3 mt-6">
<span className="min-w-[160px]"></span>
<div className="flex-1 flex justify-between pt-2 border-t border-gray-700">
<span className="text-gray-400 text-xs">{formatNumber(0)}%</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round(globalMaxValue / 4))}%</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round(globalMaxValue / 2))}%</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round((globalMaxValue * 3) / 4))}%</span>
<span className="text-gray-400 text-xs">{formatNumber(Math.round(globalMaxValue))}%</span>
</div>
<span className="min-w-[60px]"></span>
</div>
)}
</div>
</div>
);
}