fix: bugs and chnage some logic

This commit is contained in:
MehrdadAdabi 2025-10-17 20:05:59 +03:30
parent c31eba3c19
commit ac1081cdd2
10 changed files with 5378 additions and 175 deletions

View File

@ -15,6 +15,7 @@ import moment from "moment-jalaali";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } 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 { BaseCard } from "~/components/ui/base-card";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card"; import { Card, CardContent } from "~/components/ui/card";
import { Checkbox } from "~/components/ui/checkbox"; import { Checkbox } from "~/components/ui/checkbox";
@ -763,12 +764,12 @@ export function DigitalInnovationPage() {
</div> </div>
{/* Process Impacts Chart */} {/* Process Impacts Chart */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-lg w-full overflow-hidden h-[18rem]"> <BaseCard className="rounded-xl w-full overflow-hidden">
{/* <CardContent > */} {/* <CardContent > */}
<CustomBarChart <CustomBarChart
title="تاثیرات نوآوری دیجیتال به صورت درصد مقایسه ای" title="تاثیرات نوآوری دیجیتال به صورت درصد مقایسه ای"
loading={statsLoading} loading={statsLoading}
height="100%" // height="100%"
data={[ data={[
{ {
label: DigitalCardLabel.decreasCost, label: DigitalCardLabel.decreasCost,
@ -798,8 +799,7 @@ export function DigitalInnovationPage() {
barHeight="h-5" barHeight="h-5"
showAxisLabels={true} showAxisLabels={true}
/> />
{/* </CardContent> */} </BaseCard>
</Card>
</div> </div>
{/* Data Table */} {/* Data Table */}

View File

@ -44,6 +44,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import moment from "moment-jalaali"; import moment from "moment-jalaali";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { MetricCard } from "~/components/ui/metric-card";
import { useStoredDate } from "~/hooks/useStoredDate"; import { useStoredDate } from "~/hooks/useStoredDate";
import apiService from "~/lib/api"; import apiService from "~/lib/api";
import { formatCurrency } from "~/lib/utils"; import { formatCurrency } from "~/lib/utils";
@ -519,13 +520,13 @@ export function GreenInnovationPage() {
}, },
pollution: { pollution: {
value: formatNumber(parseNum(stats.pollution_reduction)), value: parseNum(stats.pollution_reduction),
percent: formatNumber(parseNum(stats.pollution_reduction_percent)), percent: parseNum(stats.pollution_reduction_percent),
}, },
waste: { waste: {
value: formatNumber(parseNum(stats.waste_reduction)), value: parseNum(stats.waste_reduction),
percent: formatNumber(parseNum(stats.waste_reductionn_percent)), percent: parseNum(stats.waste_reductionn_percent),
}, },
avarage: stats.average_project_score, avarage: stats.average_project_score,
countInnovationGreenProjects: stats.count_innovation_green_projects, countInnovationGreenProjects: stats.count_innovation_green_projects,
@ -543,7 +544,6 @@ export function GreenInnovationPage() {
setStatsLoading(false); setStatsLoading(false);
} }
}; };
const setPageData = (normalized: any) => { const setPageData = (normalized: any) => {
setSustainabilityStats((prev) => ({ setSustainabilityStats((prev) => ({
...prev, ...prev,
@ -747,39 +747,14 @@ export function GreenInnovationPage() {
</Card> </Card>
)) ))
: Object.entries(sustainabilityStats).map(([key, value]) => ( : Object.entries(sustainabilityStats).map(([key, value]) => (
<Card <MetricCard
key={key} key={key}
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50" title={value.title}
> value={Math.round(value.total.value || 0)}
<CardContent className="p-0 h-full"> valueLabel={value.total?.description}
<div className="flex flex-col justify-between gap-2 h-full"> percentValue={value.percent?.value || 0}
<div className="flex justify-between items-center border-b-2 border-gray-500/20 "> percentLabel={value.percent?.description}
<h3 className="text-lg font-bold text-white font-persian p-4"> />
{value.title}
</h3>
</div>
<div className="flex items-center justify-between p-6 flex-row-reverse">
<div className="flex flex-col">
<span className="text-3xl font-bold text-pr-green mb-1 font-persian">
% {value.percent?.value}
</span>
<span className="text-sm text-gray-400 font-persian">
{value.percent?.description}
</span>
</div>
<b className="block w-0.5 h-8 bg-gray-600 rotate-45" />
<div className="flex flex-col">
<span className="text-3xl font-bold text-pr-green mb-1 font-persian">
{value.total?.value}
</span>
<span className="text-sm text-gray-400 font-persian">
{value.total?.description}
</span>
</div>
</div>
</div>
</CardContent>
</Card>
))} ))}
</div> </div>

View File

@ -39,6 +39,7 @@ import {
ResponsiveContainer, ResponsiveContainer,
XAxis, XAxis,
} from "recharts"; } from "recharts";
import { MetricCard } from "~/components/ui/metric-card";
import { useStoredDate } from "~/hooks/useStoredDate"; import { useStoredDate } from "~/hooks/useStoredDate";
import apiService from "~/lib/api"; import apiService from "~/lib/api";
import { EventBus, formatCurrency, formatNumber } from "~/lib/utils"; import { EventBus, formatCurrency, formatNumber } from "~/lib/utils";
@ -523,15 +524,13 @@ export function InnovationBuiltInsidePage() {
const stats = data[0]; const stats = data[0];
const normalized: any = { const normalized: any = {
currencySaving: { currencySaving: {
value: formatNumber(parseNum(stats?.foreign_currency_saving)), value: parseNum(stats?.foreign_currency_saving),
percent: formatNumber( percent: parseNum(stats?.foreign_currency_saving_percent),
parseNum(stats?.foreign_currency_saving_percent)
),
}, },
investmentAmount: { investmentAmount: {
value: formatNumber(parseNum(stats?.investment_amount)), value: parseNum(stats?.investment_amount),
percent: formatNumber(parseNum(stats?.investment_amount_percent)), percent: parseNum(stats?.investment_amount_percent),
}, },
technology: { technology: {
@ -755,39 +754,47 @@ export function InnovationBuiltInsidePage() {
</Card> </Card>
)) ))
: Object.entries(sustainabilityStats).map(([key, value]) => ( : Object.entries(sustainabilityStats).map(([key, value]) => (
<Card <MetricCard
key={key} key={key}
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50" title={value.title}
> value={Math.round(value.total.value || 0)}
<CardContent className="p-0 h-full"> valueLabel={value.total?.description}
<div className="flex flex-col justify-between gap-2 h-full"> percentValue={value.percent?.value || 0}
<div className="flex justify-between items-center border-b-2 border-gray-500/20 "> percentLabel={value.percent?.description}
<h3 className="text-lg font-semibold text-white p-4"> />
{value.title} // <Card
</h3> // key={key}
</div> // className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50"
<div className="flex items-center justify-between p-6 flex-row-reverse"> // >
<div className="flex flex-col"> // <CardContent className="p-0 h-full">
<span className="text-3xl font-bold text-pr-green mb-1 font-persian"> // <div className="flex flex-col justify-between gap-2 h-full">
% {value.percent?.value} // <div className="flex justify-between items-center border-b-2 border-gray-500/20 ">
</span> // <h3 className="text-lg font-semibold text-white p-4">
<span className="text-sm text-gray-400 font-persian"> // {value.title}
{value.percent?.description} // </h3>
</span> // </div>
</div> // <div className="flex items-center justify-between p-6 flex-row-reverse">
<b className="block w-0.5 h-8 bg-gray-600 rotate-45" /> // <div className="flex flex-col">
<div className="flex flex-col"> // <span className="text-3xl font-bold text-pr-green mb-1 font-persian">
<span className="text-3xl font-bold text-pr-green mb-1 font-persian"> // % {value.percent?.value}
{value.total?.value} // </span>
</span> // <span className="text-sm text-gray-400 font-persian">
<span className="text-sm text-gray-400 font-persian"> // {value.percent?.description}
{value.total?.description} // </span>
</span> // </div>
</div> // <b className="block w-0.5 h-8 bg-gray-600 rotate-45" />
</div> // <div className="flex flex-col">
</div> // <span className="text-3xl font-bold text-pr-green mb-1 font-persian">
</CardContent> // {value.total?.value}
</Card> // </span>
// <span className="text-sm text-gray-400 font-persian">
// {value.total?.description}
// </span>
// </div>
// </div>
// </div>
// </CardContent>
// </Card>
))} ))}
{statsLoading ? ( {statsLoading ? (

View File

@ -187,11 +187,12 @@ export function ProcessInnovationPage() {
icon: DollarSign, icon: DollarSign,
color: "text-pr-green", color: "text-pr-green",
}, },
currencyreduction1: { decreaseCurrencyOperation: {
id: "currencyreduction1", id: "decreaseCurrencyOperation",
title: "کاهش هزینه عملیاتی", title: "کاهش هزینه عملیاتی",
value: formatNumber( value: formatNumber(
stats.reductionCostOprationSum.toFixed?.(0) ?? stats.reductionCostOprationSum stats.reductionCostOprationSum.toFixed?.(0) ??
stats.reductionCostOprationSum
), ),
description: "میلیون ریال یافته", description: "میلیون ریال یافته",
icon: DollarSign, icon: DollarSign,
@ -213,15 +214,6 @@ export function ProcessInnovationPage() {
const observerRef = useRef<HTMLDivElement>(null); const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false); const fetchingRef = useRef(false);
// Selection handlers
// const handleSelectAll = () => {
// if (selectedProjects.size === projects.length) {
// setSelectedProjects(new Set());
// } else {
// setSelectedProjects(new Set(projects.map((p) => p.project_no)));
// }
// };
const handleSelectProject = (projectNo: string) => { const handleSelectProject = (projectNo: string) => {
const newSelected = new Set(selectedProjects); const newSelected = new Set(selectedProjects);
if (newSelected.has(projectNo)) { if (newSelected.has(projectNo)) {
@ -489,7 +481,8 @@ export function ProcessInnovationPage() {
currencyReductionSum: parseNum(stats?.sum_reduction_value_currency), currencyReductionSum: parseNum(stats?.sum_reduction_value_currency),
frequentFailuresReductionSum: parseNum(stats?.sum_reducing_breakdowns), frequentFailuresReductionSum: parseNum(stats?.sum_reducing_breakdowns),
percentProductionStops: stats?.percent_sum_stopping_production, percentProductionStops: stats?.percent_sum_stopping_production,
percentOperatingCostBeforeInnovation: stats?.percent_operating_cost_before_innovation, percentOperatingCostBeforeInnovation:
stats?.percent_operating_cost_before_innovation,
percentBottleneckRemoval: stats?.percent_throat_removal, percentBottleneckRemoval: stats?.percent_throat_removal,
percentCurrencyReduction: stats?.percent_reduction_value_currency, percentCurrencyReduction: stats?.percent_reduction_value_currency,
percentFailuresReduction: stats?.percent_reducing_breakdowns, percentFailuresReduction: stats?.percent_reducing_breakdowns,
@ -512,6 +505,10 @@ export function ProcessInnovationPage() {
...prev.currencyreduction, ...prev.currencyreduction,
value: formatNumber(normalized.currencyReductionSum), value: formatNumber(normalized.currencyReductionSum),
}, },
decreaseCurrencyOperation: {
...prev.decreaseCurrencyOperation,
value: formatNumber(normalized.reductionCostOprationSum),
},
})); }));
setStats(normalized); setStats(normalized);
} catch (error) { } catch (error) {
@ -638,13 +635,14 @@ export function ProcessInnovationPage() {
<div className="flex gap-4"> <div className="flex gap-4">
<div className="space-y-4 w-full"> <div className="space-y-4 w-full">
{/* Stats Grid */} {/* Stats Grid */}
<div className="grid grid-cols-2 gap-3"> <div className="h-full">
{loading || statsLoading {loading || statsLoading ? (
? // Loading skeleton for stats cards - matching new design // Skeleton cards
Array.from({ length: 4 }).map((_, index) => ( <div className="flex flex-wrap justify-between gap-3">
{Array.from({ length: 6 }).map((_, index) => (
<BaseCard <BaseCard
key={`skeleton-${index}`} key={`skeleton-${index}`}
className="rounded-2xl overflow-hidden" className="rounded-2xl overflow-hidden w-full sm:w-[48%] md:w-[30%]"
> >
<div className="flex flex-col justify-between gap-2"> <div className="flex flex-col justify-between gap-2">
<div className="flex 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">
@ -668,42 +666,112 @@ export function ProcessInnovationPage() {
</div> </div>
</div> </div>
</BaseCard> </BaseCard>
)) ))}
: Object.entries(stateCard).map(([key, card]) => { </div>
// map percent values for each card key ) : (
const percentMap: Record< <div className="flex flex-col h-full gap-5">
string, <div className="flex flex-row gap-4 h-full">
number | string | undefined
> = {
productionstopsprevention: stats.percentProductionStops,
bottleneckremoval: stats.percentBottleneckRemoval,
currencyreduction: stats.percentCurrencyReduction,
frequentfailuresreduction: stats.percentFailuresReduction,
};
const percentValue = percentMap[key];
return (
<BaseCard <BaseCard
key={card.id} key={stateCard.bottleneckremoval.id}
title={card.title} title={stateCard.bottleneckremoval.title}
className="border-gray-700/50" className="border-gray-700/50 w-full"
icon={card.icon} icon={stateCard.bottleneckremoval.icon}
> >
<div className="flex items-center justify-center flex-col"> <div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="text-center"> <div className="text-center">
<p className="text-3xl text-pr-green font-bold mb-1"> <p className="text-3xl text-pr-green font-bold mb-1">
{card.value} {stateCard.bottleneckremoval.value}
</p> </p>
<div className="text-[11px] text-[#ACACAC] font-light font-persian"> <div className="text-[11px] text-[#ACACAC] font-light font-persian">
{card.description} {stateCard.bottleneckremoval.description}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</BaseCard> </BaseCard>
);
})} <BaseCard
key={stateCard.currencyreduction.id}
title={stateCard.currencyreduction.title}
className="border-gray-700/50 w-full"
icon={stateCard.currencyreduction.icon}
>
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-3xl text-pr-green font-bold mb-1">
{stateCard.currencyreduction.value}
</p>
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
{stateCard.currencyreduction.description}
</div>
</div>
</div>
</div>
</BaseCard>
</div>
<div className="flex flex-row gap-4 h-full">
<BaseCard
key={stateCard.frequentfailuresreduction.id}
title={stateCard.frequentfailuresreduction.title}
className="border-gray-700/50 w-full"
icon={stateCard.frequentfailuresreduction.icon}
>
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-3xl text-pr-green font-bold mb-1">
{stateCard.frequentfailuresreduction.value}
</p>
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
{stateCard.frequentfailuresreduction.description}
</div>
</div>
</div>
</div>
</BaseCard>
<BaseCard
key={stateCard.decreaseCurrencyOperation.id}
title={stateCard.decreaseCurrencyOperation.title}
className="border-gray-700/50 w-full"
icon={stateCard.decreaseCurrencyOperation.icon}
>
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-3xl text-pr-green font-bold mb-1">
{stateCard.decreaseCurrencyOperation.value}
</p>
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
{stateCard.decreaseCurrencyOperation.description}
</div>
</div>
</div>
</div>
</BaseCard>
<BaseCard
key={stateCard.productionstopsprevention.id}
title={stateCard.productionstopsprevention.title}
className="border-gray-700/50 w-full"
icon={stateCard.productionstopsprevention.icon}
>
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-3xl text-pr-green font-bold mb-1">
{stateCard.productionstopsprevention.value}
</p>
<div className="text-[11px] text-[#ACACAC] font-light font-persian">
{stateCard.productionstopsprevention.description}
</div>
</div>
</div>
</div>
</BaseCard>
</div>
</div>
)}
</div> </div>
</div> </div>
@ -736,7 +804,8 @@ export function ProcessInnovationPage() {
}, },
{ {
label: "کاهش هزینه عملیاتی", label: "کاهش هزینه عملیاتی",
value: Number(stats.percentOperatingCostBeforeInnovation) || 0, value:
Number(stats.percentOperatingCostBeforeInnovation) || 0,
labelColor: "text-white", labelColor: "text-white",
}, },
{ {
@ -745,7 +814,7 @@ export function ProcessInnovationPage() {
labelColor: "text-white", labelColor: "text-white",
}, },
]} ]}
barHeight="h-6" barHeight="h-5"
showAxisLabels={true} showAxisLabels={true}
/> />
</BaseCard> </BaseCard>

View File

@ -105,8 +105,8 @@ export function NetworkGraph({
return await apiService.call<any>({ return await apiService.call<any>({
get_values_workflow_function: { get_values_workflow_function: {
stage_id: stage_id, stage_id: stage_id,
start_date: date?.start || null, // start_date: date?.start || null,
end_date: date?.end || null, // end_date: date?.end || null,
}, },
}); });
}, },
@ -194,7 +194,7 @@ export function NetworkGraph({
aborted = true; aborted = true;
controller.abort(); controller.abort();
}; };
}, [isMounted, token, getImageUrl]); }, [isMounted, token, getImageUrl, date]);
useEffect(() => { useEffect(() => {
if (!isMounted || !svgRef.current || isLoading || nodes.length === 0) if (!isMounted || !svgRef.current || isLoading || nodes.length === 0)

View File

@ -23,22 +23,34 @@ export function BaseCard({
return ( return (
<Card <Card
className={cn( className={cn(
"bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm py-4 grid items-center", "bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm py-2 pb-0 grid items-center",
className className
)} )}
> >
{Icon && title ? ( {Icon && title ? (
<CardHeader className={cn("border-b-2 border-gray-500/20 py-2 px-0 pb-4", headerClassName)}> <CardHeader
<CardTitle className="text-white text-sm text-right font-persian px-4 my-auto items-center flex w-full justify-between">{title} {<Icon />} </CardTitle> className={cn(
"border-b-2 border-gray-500/20 py-2 px-0 pb-4",
headerClassName
)}
>
<CardTitle className="text-white text-sm text-right font-persian px-4 my-auto items-center flex w-full justify-between">
{title} {<Icon />}{" "}
</CardTitle>
</CardHeader> </CardHeader>
) : ) : withHeader && title ? (
withHeader && title ? ( <CardHeader
<CardHeader className={cn("pb-2 border-b-2 border-gray-500/20", headerClassName)}> className={cn("pb-2 border-b-2 border-gray-500/20", headerClassName)}
<CardTitle className="text-white text-sm text-right font-persian px-4">{title}</CardTitle> >
<CardTitle className="text-white text-sm text-right font-persian px-4">
{title}
</CardTitle>
</CardHeader> </CardHeader>
) : title ? ( ) : title ? (
<div className="border-b-2 border-gray-500/20 pb-2"> <div className="border-b-2 border-gray-500/20 pb-2">
<h3 className="text-sm font-bold text-white text-right font-persian px-4">{title}</h3> <h3 className="text-sm font-bold text-white text-right font-persian px-4">
{title}
</h3>
</div> </div>
) : null} ) : null}
<CardContent className={cn("py-2 px-4 ", contentClassName)}> <CardContent className={cn("py-2 px-4 ", contentClassName)}>

View File

@ -39,7 +39,7 @@ export function CustomBarChart({
// Loading skeleton // Loading skeleton
if (loading) { if (loading) {
return ( return (
<div className={`space-y-6 p-4 ${className}`} style={{ height }}> <div className={`space-y-6 p-4 pt-0 ${className}`} style={{ height }}>
{title && ( {title && (
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div> <div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
)} )}
@ -71,7 +71,7 @@ export function CustomBarChart({
<div className={`space-y-6 ${className}`} style={{ height }}> <div className={`space-y-6 ${className}`} style={{ height }}>
{title && ( {title && (
<div className="border-b-[#3F415A] border-b-2"> <div className="border-b-[#3F415A] border-b-2">
<h3 className="text-sm font-semibold text-white font-persian text-right p-4"> <h3 className="text-sm font-semibold text-white font-persian text-right px-4 pb-3">
{title} {title}
</h3> </h3>
</div> </div>

View File

@ -17,9 +17,9 @@ export function MetricCard({
percentLabel = "درصد به کل", percentLabel = "درصد به کل",
}: MetricCardProps) { }: MetricCardProps) {
return ( return (
<BaseCard title={title}> <BaseCard title={title} className="h-full">
<div className="flex items-center justify-center flex-col"> <div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4 h-full">
<div className="text-center"> <div className="text-center">
<p className="text-3xl font-bold text-green-400"> <p className="text-3xl font-bold text-green-400">
{formatNumber(value)} {formatNumber(value)}

5140
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff