@ -22,10 +22,6 @@ import {
ChevronUp ,
ChevronDown ,
RefreshCw ,
TrendingUp ,
TrendingDown ,
Target ,
BarChart3 ,
ExternalLink ,
} from "lucide-react" ;
import apiService from "~/lib/api" ;
@ -57,6 +53,19 @@ interface StatsCard {
color : string ;
}
interface InnovationStats {
totalProjects : number ;
averageScore : number ;
productionStopsPreventionSum : number ; // مجموع جلوگیری از توقفات تولید
bottleneckRemovalCount : number ; // تعداد رفع گلوگاه
currencyReductionSum : number ; // مجموع کاهش ارز بری (میلیون ریال)
frequentFailuresReductionSum : number ; // مجموع کاهش خرابی های پرتکرار
percentProductionStops : number ; // درصد مقایسهای جلوگیری از توقفات تولید
percentBottleneckRemoval : number ; // درصد مقایسهای رفع گلوگاه
percentCurrencyReduction : number ; // درصد مقایسهای کاهش ارز بری
percentFailuresReduction : number ; // درصد مقایسهای کاهش خرابیهای پرتکرار
}
const columns = [
{ key : "select" , label : "" , sortable : false , width : "50px" } ,
{ key : "project_no" , label : "شماره پروژه" , sortable : true , width : "140px" } ,
@ -75,6 +84,19 @@ export function ProcessInnovationPage() {
const [ hasMore , setHasMore ] = useState ( true ) ;
const [ totalCount , setTotalCount ] = useState ( 0 ) ;
const [ actualTotalCount , setActualTotalCount ] = useState ( 0 ) ;
const [ statsLoading , setStatsLoading ] = useState ( false ) ;
const [ stats , setStats ] = useState < InnovationStats > ( {
totalProjects : 0 ,
averageScore : 0 ,
productionStopsPreventionSum : 0 ,
bottleneckRemovalCount : 0 ,
currencyReductionSum : 0 ,
frequentFailuresReductionSum : 0 ,
percentProductionStops : 0 ,
percentBottleneckRemoval : 0 ,
percentCurrencyReduction : 0 ,
percentFailuresReduction : 0 ,
} ) ;
const [ sortConfig , setSortConfig ] = useState < SortConfig > ( {
field : "start_date" ,
direction : "asc" ,
@ -121,10 +143,7 @@ export function ProcessInnovationPage() {
{
id : "production-stops-prevention" ,
title : "جلوگیری از توقفات تولید" ,
value : formatNumber ( projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 )
. toFixed ( 1 ) ) ,
value : formatNumber ( stats . productionStopsPreventionSum . toFixed ? . ( 1 ) ? ? stats . productionStopsPreventionSum ) ,
description : "ظرفیت افزایش یافته" ,
icon : < CirclePause / > ,
color : "text-emerald-400" ,
@ -132,7 +151,7 @@ export function ProcessInnovationPage() {
{
id : "bottleneck-removal" ,
title : "رفع گلوگاه" ,
value : formatNumber ( projects. filter ( p = > p . throat_removal === "بله" ) . length ) ,
value : formatNumber ( stats. bottleneckRemovalCount ) ,
description : "تعداد رفع گلوگاه" ,
icon : < Funnel / > ,
color : "text-emerald-400"
@ -141,21 +160,15 @@ export function ProcessInnovationPage() {
{
id : "currency-reduction" ,
title : "کاهش ارز بری" ,
value : formatNumber ( projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 )
. toFixed ( 0 ) ) ,
description : "میلیون ریال کاهش یافته" ,
value : formatNumber ( stats . currencyReductionSum . toFixed ? . ( 0 ) ? ? stats . currencyReductionSum ) ,
description : "دلار کاهش یافته" ,
icon : < DollarSign / > ,
color : "text-emerald-400" ,
} ,
{
id : "frequent-failures-reduction" ,
title : "کاهش خرابیهای پرتکرار" ,
value : formatNumber ( projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 )
. toFixed ( 1 ) ) ,
value : formatNumber ( stats . frequentFailuresReductionSum . toFixed ? . ( 1 ) ? ? stats . frequentFailuresReductionSum ) ,
description : "مجموع درصد کاهش خرابی" ,
icon : < Wrench / > ,
color : "text-emerald-400" ,
@ -191,6 +204,7 @@ export function ProcessInnovationPage() {
"amount_currency_reduction" ,
"Reduce_rate_failure" ,
] ,
Pagination : { PageNumber : pageToFetch , PageSize : pageSize } ,
Sorts : [ [ sortConfig . field , sortConfig . direction ] ] ,
Conditions : [ [ "type_of_innovation" , "=" , "نوآوری در فرآیند" ] ] ,
} ) ;
@ -263,6 +277,7 @@ export function ProcessInnovationPage() {
useEffect ( ( ) = > {
fetchProjects ( true ) ;
fetchTotalCount ( ) ;
fetchStats ( ) ;
} , [ sortConfig ] ) ;
useEffect ( ( ) = > {
@ -322,7 +337,10 @@ export function ProcessInnovationPage() {
try {
const parsedData = JSON . parse ( dataString ) ;
if ( Array . isArray ( parsedData ) && parsedData [ 0 ] ) {
setActualTotalCount ( parsedData [ 0 ] . project_no_count || 0 ) ;
const count = parsedData [ 0 ] . project_no_count || 0 ;
setActualTotalCount ( count ) ;
// Keep stats in sync if backend stats not yet loaded
setStats ( ( prev ) = > ( { . . . prev , totalProjects : count } ) ) ;
}
} catch ( parseError ) {
console . error ( "Error parsing count data:" , parseError ) ;
@ -334,6 +352,52 @@ export function ProcessInnovationPage() {
}
} ;
// Fetch aggregated stats from backend call API (innovation_process_function)
const fetchStats = async ( ) = > {
try {
setStatsLoading ( true ) ;
const raw = await apiService . callInnovationProcess < any > ( {
innovation_process_function : {
} ,
} ) ;
let payload : any = raw ? . data ;
if ( typeof payload === "string" ) {
try { payload = JSON . parse ( payload ) ; } catch { }
}
const parseNum = ( v : unknown ) : number = > {
if ( v == null ) return 0 ;
if ( typeof v === "number" ) return v ;
if ( typeof v === "string" ) {
const cleaned = v . replace ( /,/g , "" ) . trim ( ) ;
const n = parseFloat ( cleaned ) ;
return isNaN ( n ) ? 0 : n ;
}
return 0 ;
} ;
const normalized : InnovationStats = {
totalProjects : parseNum ( payload ? . count_innovation_process_projects ) ,
averageScore : parseNum ( payload ? . average_project_score ) ,
productionStopsPreventionSum : parseNum ( payload ? . sum_stopping_production ) ,
bottleneckRemovalCount : parseNum ( payload ? . count_throat_removal ) ,
currencyReductionSum : parseNum ( payload ? . sum_reduction_value_currency ) ,
frequentFailuresReductionSum : parseNum ( payload ? . sum_reducing_breakdowns ) ,
percentProductionStops : parseNum ( payload ? . percent_sum_stopping_production ) ,
percentBottleneckRemoval : parseNum ( payload ? . percent_throat_removal ) ,
percentCurrencyReduction : parseNum ( payload ? . percent_reduction_value_currency ) ,
percentFailuresReduction : parseNum ( payload ? . percent_reducing_breakdowns ) ,
} ;
setStats ( normalized ) ;
} catch ( error ) {
console . error ( "Error fetching stats:" , error ) ;
} finally {
setStatsLoading ( false ) ;
}
} ;
const handleRefresh = ( ) = > {
fetchingRef . current = false ;
setCurrentPage ( 1 ) ;
@ -341,6 +405,7 @@ export function ProcessInnovationPage() {
setHasMore ( true ) ;
fetchProjects ( true ) ;
fetchTotalCount ( ) ;
fetchStats ( ) ;
} ;
const formatCurrency = ( amount : string | number ) = > {
@ -468,13 +533,13 @@ export function ProcessInnovationPage() {
< div className = "space-y-6 w-full" >
{ /* Stats Grid */ }
< div className = "grid grid-cols-2 gap-3" >
{ loading ? (
{ loading || statsLoading ? (
// Loading skeleton for stats cards - matching new design
Array . from ( { length : 4 } ) . map ( ( _ , index ) = > (
< Card key = { ` skeleton- ${ index } ` } className = "bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden" >
< CardContent className = "p-2" >
< div className = "flex flex-col justify-between gap-2" >
< div className = "flex p-2 justify-between items-center border-b-2 mx-4 border-gray-500/20">
< div className = "flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
< div className = "h-6 bg-gray-600 rounded animate-pulse" style = { { width : '60%' } } / >
< div className = "p-3 bg-emerald-500/20 rounded-full w-fit" >
< div className = "w-6 h-6 bg-gray-600 rounded animate-pulse" / >
@ -493,16 +558,16 @@ export function ProcessInnovationPage() {
< Card key = { card . id } className = "bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50" >
< CardContent className = "p-2" >
< div className = "flex flex-col justify-between gap-2" >
< div className = "flex p-2 justify-between items-center border-b-2 mx-4 border-gray-500/20">
< h3 className = "text-lg font-bold text-white font-persian mb-2 ">
< div className = "flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
< h3 className = "text-lg font-bold text-white font-persian ">
{ card . title }
< / h3 >
< div className = { ` p-3 ${ card . bgColor } gird placeitems-center rounded-full w-fit ` } >
< div className = { ` p-3 gird placeitems-center rounded-full w-fit ` } >
{ card . icon }
< / div >
< / div >
< div className = "flex items-center justify-center flex-col p-1" >
< p className = { ` text-3xl font-bold ${ card . color } mb-1 ` } >
< p className = { ` text-3xl font-bold ${ card . color } mb-1 ` } >
{ card . value }
< / p >
< p className = "text-sm text-gray-300 font-persian" >
@ -520,8 +585,8 @@ export function ProcessInnovationPage() {
{ /* Process Impacts Chart */ }
< Card className = "bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden" >
< CardContent className = "p- 6 ">
< div className = "mb- 6 ">
< CardContent className = "p- 4 ">
< div className = "mb- 4 ">
< h3 className = "text-xl font-bold text-white font-persian text-right mb-2" >
ت ا ث ی ر ا ت ف ر آ ی ن د ی ب ه ص و ر ت د ر ص د م ق ا ی س ه ا ی
< / h3 >
@ -536,21 +601,13 @@ export function ProcessInnovationPage() {
< div
className = "bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out"
style = { {
width : ` ${ Math . min (
projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 ) / 10 ,
100
) } % `
width : ` ${ Math . min ( ( stats . percentProductionStops || 0 ) , 100 ) } % `
} }
>
< / div >
< / div >
< span className = "text-emerald-400 font-bold text-sm min-w-[40px] text-left" >
{ formatNumber ( projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 )
. toFixed ( 1 ) ) } %
{ formatNumber ( ( ( stats . percentProductionStops ? ? 0 ) as number ) . toFixed ? . ( 1 ) ? ? ( stats . percentProductionStops ? ? 0 ) ) } %
< / span >
< / div >
@ -561,16 +618,13 @@ export function ProcessInnovationPage() {
< div
className = "bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out"
style = { {
width : ` ${ Math . min (
projects . filter ( p = > p . throat_removal === "بله" ) . length * 10 ,
100
) } % `
width : ` ${ Math . min ( ( stats . percentBottleneckRemoval || 0 ) , 100 ) } % `
} }
>
< / div >
< / div >
< span className = "text-emerald-400 font-bold text-sm min-w-[40px] text-left" >
{ formatNumber ( projects . filter ( p = > p . throat_removal === "بله" ) . length ) } %
{ formatNumber ( ( ( stats . percentBottleneckRemoval ? ? 0 ) as number ) . toFixed ? . ( 1 ) ? ? ( stats . percentBottleneckRemoval ? ? 0 ) ) } %
< / span >
< / div >
@ -581,21 +635,13 @@ export function ProcessInnovationPage() {
< div
className = "bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out"
style = { {
width : ` ${ Math . min (
projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 ) / 100 ,
100
) } % `
width : ` ${ Math . min ( ( stats . percentCurrencyReduction || 0 ) , 100 ) } % `
} }
>
< / div >
< / div >
< span className = "text-emerald-400 font-bold text-sm min-w-[40px] text-left" >
{ formatNumber ( projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 )
. toFixed ( 0 ) ) } %
{ formatNumber ( ( ( stats . percentCurrencyReduction ? ? 0 ) as number ) . toFixed ? . ( 0 ) ? ? ( stats . percentCurrencyReduction ? ? 0 ) ) } %
< / span >
< / div >
@ -606,21 +652,13 @@ export function ProcessInnovationPage() {
< div
className = "bg-emerald-400 h-5 rounded-full transition-all duration-500 ease-out"
style = { {
width : ` ${ Math . min (
projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 ) / 10 ,
100
) } % `
width : ` ${ Math . min ( ( stats . percentFailuresReduction || 0 ) , 100 ) } % `
} }
>
< / div >
< / div >
< span className = "text-emerald-400 font-bold text-sm min-w-[40px] text-left" >
{ formatNumber ( projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 )
. toFixed ( 1 ) ) } %
{ formatNumber ( ( ( stats . percentFailuresReduction ? ? 0 ) as number ) . toFixed ? . ( 1 ) ? ? ( stats . percentFailuresReduction ? ? 0 ) ) } %
< / span >
< / div >
< / div >
@ -628,62 +666,23 @@ export function ProcessInnovationPage() {
{ /* Percentage Scale */ }
< div className = "flex justify-between mt-6 pt-4 border-t border-gray-700" >
< span className = "text-gray-400 text-xs" > ۰ ٪ < / span >
< span className = "text-gray-400 text-xs" >
{ formatNumber ( Math . round ( Math . max (
projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 ) / 10 ,
projects . filter ( p = > p . throat_removal === "بله" ) . length * 10 ,
projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 ) / 100 ,
projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 ) / 10
) / 4 ) ) } ٪
< / span >
< span className = "text-gray-400 text-xs" >
{ formatNumber ( Math . round ( Math . max (
projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 ) / 10 ,
projects . filter ( p = > p . throat_removal === "بله" ) . length * 10 ,
projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 ) / 100 ,
projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 ) / 10
) / 2 ) ) } ٪
< / span >
< span className = "text-gray-400 text-xs" >
{ formatNumber ( Math . round ( Math . max (
projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 ) / 10 ,
projects . filter ( p = > p . throat_removal === "بله" ) . length * 10 ,
projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 ) / 100 ,
projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 ) / 10
) * 3 / 4 ) ) } ٪
< / span >
< span className = "text-gray-400 text-xs" >
{ formatNumber ( Math . round ( Math . max (
projects
. filter ( p = > p . reduce_prevention_production_stops && parseFloat ( p . reduce_prevention_production_stops ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . reduce_prevention_production_stops ) , 0 ) / 10 ,
projects . filter ( p = > p . throat_removal === "بله" ) . length * 10 ,
projects
. filter ( p = > p . amount_currency_reduction && parseFloat ( p . amount_currency_reduction ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . amount_currency_reduction ) , 0 ) / 100 ,
projects
. filter ( p = > p . Reduce_rate_failure && parseFloat ( p . Reduce_rate_failure ) > 0 )
. reduce ( ( sum , p ) = > sum + parseFloat ( p . Reduce_rate_failure ) , 0 ) / 10
) ) ) } ٪
< / 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 >
< / CardContent >
< / Card >
@ -693,14 +692,13 @@ export function ProcessInnovationPage() {
< Card className = "bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden" >
< CardContent className = "p-0" >
< div className = "relative" >
< div className = "overflow-auto max-h-[calc(100vh-400px)]" >
< Table >
< Table containerClassName = "overflow-auto custom-scrollbar max-h-[calc(90vh-400px)]" >
< TableHeader >
< TableRow className = "bg-[#3F415A]" >
{ columns . map ( ( column ) = > (
< TableHead
key = { column . key }
className = "text-right font-persian whitespace-nowrap text-gray-200 font-medium "
className = "text-right font-persian whitespace-nowrap text-gray-200 font-medium sticky top-0 z-20 bg-[#3F415A] "
style = { { width : column.width } }
>
{ column . key === "select" ? (
@ -714,7 +712,7 @@ export function ProcessInnovationPage() {
) : column . sortable ? (
< button
onClick = { ( ) = > handleSort ( column . key ) }
className = "flex items-center gap-2 hover:text-emerald-400 transition-colors "
className = "flex items-center gap-2 "
>
< span > { column . label } < / span >
{ sortConfig . field === column . key ? (
@ -736,17 +734,17 @@ export function ProcessInnovationPage() {
< / TableHeader >
< TableBody >
{ loading ? (
// Skeleton loading rows
// Skeleton loading rows (compact)
Array . from ( { length : 10 } ) . map ( ( _ , index ) = > (
< TableRow key = { ` skeleton- ${ index } ` } >
< TableRow key = { ` skeleton- ${ index } ` } className = "text-sm leading-tight h-8" >
{ columns . map ( ( column ) = > (
< TableCell
key = { column . key }
className = "text-right whitespace-nowrap border-emerald-500/20 "
className = "text-right whitespace-nowrap border-emerald-500/20 py-1 px-2 "
>
< div className = "flex items-center gap-2" >
< div className = "w- 3 h-3 bg-gray-600 rounded-full animate-pulse" / >
< div className = "h- 4 bg-gray-600 rounded animate-pulse" style = { { width : ` ${ Math . random ( ) * 60 + 40 } % ` } } / >
< div className = "w- 2.5 h-2.5 bg-gray-600 rounded-full animate-pulse" / >
< div className = "h- 2.5 bg-gray-600 rounded animate-pulse" style = { { width : ` ${ Math . random ( ) * 60 + 40 } % ` } } / >
< / div >
< / TableCell >
) ) }
@ -765,11 +763,11 @@ export function ProcessInnovationPage() {
< / TableRow >
) : (
projects . map ( ( project , index ) = > (
< TableRow key = { ` ${ project . project_no } - ${ index } ` } >
< TableRow key = { ` ${ project . project_no } - ${ index } ` } className = "text-sm leading-tight h-8" >
{ columns . map ( ( column ) = > (
< TableCell
key = { column . key }
className = { ` text-right whitespace-nowrap border-emerald-500/20 ${ column . key === "select" ? "flex justify-center items-center" : "" } ` }
className = { ` text-right whitespace-nowrap border-emerald-500/20 py-1 px-2 ${ column . key === "select" ? "flex justify-center items-center" : "" } ` }
>
{ renderCellContent ( project , column ) }
< / TableCell >
@ -779,13 +777,12 @@ export function ProcessInnovationPage() {
) }
< / TableBody >
< / Table >
< / div >
< / div >
{ /* Infinite scroll trigger */ }
< div ref = { observerRef } className = "h-auto" >
{ loadingMore && (
< div className = "flex items-center justify-center py- 4 ">
< div className = "flex items-center justify-center py- 1 ">
< div className = "flex items-center gap-2" >
< RefreshCw className = "w-4 h-4 animate-spin text-emerald-400" / >
< span className = "font-persian text-gray-300 text-xs" >
@ -823,7 +820,7 @@ export function ProcessInnovationPage() {
< div className = "p-2 px-4 bg-gray-700/50" >
< div className = "grid grid-cols-6 gap-4 text-sm text-gray-300 font-persian" >
< div className = "text-center gap-2 items-center flex" >
< div className = "text-base text-gray-401 mb-1" > ک ل پ ر و ژ ه ه ا : { formatNumber ( actualTotalCount ) } < / div >
< div className = "text-base text-gray-401 mb-1" > ک ل پ ر و ژ ه ه ا : { formatNumber ( stats . totalProjects || actualTotalCount ) } < / div >
< / div >
{ /* Project number column - empty */ }
< div > < / div >
@ -835,13 +832,7 @@ export function ProcessInnovationPage() {
< div className = "flex justify-center items-center gap-2" >
< div className = "text-base text-gray-400 mb-1" > م ی ا ن گ ی ن امتیاز : < / div >
< div className = "font-bold" >
{ ( ( ) = > {
if ( projects . length === 0 ) return formatNumber ( 0 ) ;
const validRatings = projects . filter ( p = > p . project_rating && ! isNaN ( parseFloat ( p . project_rating ) ) ) ;
if ( validRatings . length === 0 ) return formatNumber ( 0 ) ;
const average = validRatings . reduce ( ( sum , p ) = > sum + parseFloat ( p . project_rating ) , 0 ) / validRatings . length ;
return formatNumber ( average . toFixed ( 1 ) ) ;
} ) ( ) }
{ formatNumber ( ( ( stats . averageScore ? ? 0 ) as number ) . toFixed ? . ( 1 ) ? ? ( stats . averageScore ? ? 0 ) ) }
< / div >
< / div >