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 toast from "react-hot-toast";
import { Badge } from "~/components/ui/badge";
import { BaseCard } from "~/components/ui/base-card";
import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card";
import { Checkbox } from "~/components/ui/checkbox";
@ -763,12 +764,12 @@ export function DigitalInnovationPage() {
</div>
{/* 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 > */}
<CustomBarChart
title="تاثیرات نوآوری دیجیتال به صورت درصد مقایسه ای"
loading={statsLoading}
height="100%"
// height="100%"
data={[
{
label: DigitalCardLabel.decreasCost,
@ -798,8 +799,7 @@ export function DigitalInnovationPage() {
barHeight="h-5"
showAxisLabels={true}
/>
{/* </CardContent> */}
</Card>
</BaseCard>
</div>
{/* Data Table */}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ interface BaseCardProps {
headerClassName?: string;
contentClassName?: string;
children: React.ReactNode;
icon ?: React.ComponentType<{ className?: string }>;
icon?: React.ComponentType<{ className?: string }>;
withHeader?: boolean;
}
@ -18,30 +18,42 @@ export function BaseCard({
contentClassName,
children,
withHeader = false,
icon : Icon,
icon: Icon,
}: BaseCardProps) {
return (
<Card
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
)}
>
{Icon && title ? (
<CardHeader 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
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>
) :
withHeader && title ? (
<CardHeader 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>
) : withHeader && title ? (
<CardHeader
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>
</CardHeader>
) : title ? (
<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>
) : null}
<CardContent className={cn("py-2 px-4", contentClassName)}>
<CardContent className={cn("py-2 px-4 ", contentClassName)}>
{children}
</CardContent>
</Card>

View File

@ -9,7 +9,7 @@ const Card = React.forwardRef<
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm ",
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}

View File

@ -39,7 +39,7 @@ export function CustomBarChart({
// Loading skeleton
if (loading) {
return (
<div className={`space-y-6 p-4 ${className}`} style={{ height }}>
<div className={`space-y-6 p-4 pt-0 ${className}`} style={{ height }}>
{title && (
<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 }}>
{title && (
<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}
</h3>
</div>

View File

@ -17,32 +17,32 @@ export function MetricCard({
percentLabel = "درصد به کل",
}: MetricCardProps) {
return (
<BaseCard title={title}>
<BaseCard title={title} className="h-full">
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-3xl font-bold text-green-400">
{formatNumber(value)}
</p>
<div className="text-xs text-gray-400 font-persian">
{valueLabel}
</div>
</div>
{percentValue !== undefined && (
<>
<span className="text-5xl font-thin text-gray-600">/</span>
<div className="text-center">
<p className="text-3xl font-bold text-green-400">
{formatNumber(percentValue)}%
</p>
<div className="text-xs text-gray-400 font-persian">
{percentLabel}
</div>
</div>
</>
)}
<div className="flex items-center gap-4 h-full">
<div className="text-center">
<p className="text-3xl font-bold text-green-400">
{formatNumber(value)}
</p>
<div className="text-xs text-gray-400 font-persian">
{valueLabel}
</div>
</div>
</BaseCard>
{percentValue !== undefined && (
<>
<span className="text-5xl font-thin text-gray-600">/</span>
<div className="text-center">
<p className="text-3xl font-bold text-green-400">
{formatNumber(percentValue)}%
</p>
<div className="text-xs text-gray-400 font-persian">
{percentLabel}
</div>
</div>
</>
)}
</div>
</div>
</BaseCard>
);
}

5140
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff