feat/green-innovation #6

Merged
Saeed0920 merged 11 commits from feat/green-innovation into main 2025-08-30 16:34:22 +03:30
Showing only changes of commit bf33d50d81 - Show all commits

View File

@ -21,19 +21,34 @@ import {
DialogTitle,
} from "~/components/ui/dialog";
import {
ChevronUp,
ChevronDown,
RefreshCw,
ExternalLink,
Building2,
PickaxeIcon,
UserIcon,
UsersIcon,
} from "lucide-react";
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
LineChart,
Line,
Rectangle,
Legend,
} from "recharts";
import apiService from "~/lib/api";
import toast from "react-hot-toast";
import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react";
import ProjectDetail from "../projects/project-detail";
import {
LoaderCircle,
TrendingUp,
Key,
Sparkle,
Zap,
Flame,
Building2,
PickaxeIcon,
UsersIcon,
UserIcon,
RefreshCw,
} from "lucide-react";
import DashboardLayout from "../layout";
moment.loadPersian({ usePersianDigits: true });
@ -80,6 +95,19 @@ interface InnovationStats {
percentFailuresReduction: number; // درصد مقایسه‌ای کاهش خرابی‌های پرتکرار
}
interface Params {
icon: any;
label: string;
value: string;
suffix: string;
}
interface RecycleParams {
water: Params;
food: Params;
power: Params;
oil: Params;
}
const columns = [
{ key: "select", label: "", sortable: false, width: "50px" },
{ key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" },
@ -131,6 +159,32 @@ export function GreenInnovationPage() {
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
const [selectedProjectDetails, setSelectedProjectDetails] =
useState<ProcessInnovationData | null>(null);
const [recycleParams, setRecycleParams] = useState<RecycleParams>({
water: {
icon: <Key className="text-emerald-400" size={"18px"} />,
label: "آب",
value: "1,520",
suffix: "لیتر",
},
food: {
icon: <Sparkle className="text-emerald-400" size={"18px"} />,
label: "خوراک",
value: "520",
suffix: "تن",
},
oil: {
icon: <Flame className="text-emerald-400" size={"18px"} />,
label: "سوخت",
value: "250",
suffix: "متر مربع",
},
power: {
icon: <Zap className="text-emerald-400" size={"18px"} />,
label: "برق",
value: "650",
suffix: "میلیون مگاوات",
},
});
const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false);
@ -579,14 +633,20 @@ export function GreenInnovationPage() {
}
};
const data = [
{ name: recycleParams.water.label, pv: 70, amt: 80 },
{ name: recycleParams.power.label, pv: 45, amt: 60 },
{ name: recycleParams.oil.label, pv: 90, amt: 75 },
{ name: recycleParams.food.label, pv: 30, amt: 50 },
];
return (
<DashboardLayout title="نوآوری سبز">
<div className="p-6 space-y-4">
{/* Stats Cards */}
<div className="flex gap-6">
<div className="space-y-6 w-full">
<div className="flex gap-6 mb-6">
<div className="flex flex-col gap-11 w-full h-full">
{/* Stats Grid */}
<div className="flex flex-col gap-3">
{loading || statsLoading
? // Loading skeleton for stats cards - matching new design
Array.from({ length: 2 }).map((_, index) => (
@ -595,7 +655,7 @@ export function GreenInnovationPage() {
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 flex-col justify-between gap-2 h-full">
<div className="flex justify-between items-center border-b-2 border-gray-500/20">
<div
className="h-6 bg-gray-600 rounded animate-pulse"
@ -624,64 +684,153 @@ export function GreenInnovationPage() {
key={card.id}
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
>
<CardContent className="p-0">
<div className="flex flex-col justify-between gap-2">
<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">
{card.title}
</h3>
</div>
<div className="flex items-center justify-between p-6">
<span className="text-emerald-400">
<div className="flex items-center justify-between p-6 flex-row-reverse">
<div className="flex flex-col">
<span className="text-3xl font-bold text-emerald-400 mb-1">
% {card.percent.value}
</span>
<span className="text-sm text-gray-400 font-persian">
{card.percent.description}
</span>
</div>
<b className="block w-0.5 h-12 bg-gray-600 rotate-45" />
<div className="flex flex-col">
<span className="text-3xl font-bold text-emerald-400 mb-1">
{card.total.value}
</span>
<b className="block w-0.5 h-8 bg-emerald-400 rotate-45" />
<span className="text-emerald-400">
{card.percent.value}
<span className="text-sm text-gray-400 font-persian">
{card.total.description}
</span>
</div>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* 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-4">
<CustomBarChart
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
loading={statsLoading}
data={[
{
label: "کاهش توقفات تولید",
value: stats.percentProductionStops || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
{
label: "رفع گلوگاه تولید",
value: stats.percentBottleneckRemoval || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
{
label: "کاهش ارز بری",
value: stats.percentCurrencyReduction || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
{
label: "کاهش خرابی پر تکرار",
value: stats.percentFailuresReduction || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
]}
barHeight="h-5"
showAxisLabels={true}
<CardContent className="p-0 h-full overflow-hidden">
<div className="border-b-2 border-gray-500/20">
<div className="w-full p-4 px-6">
<span>بازیافت و بازیابی منابع</span>
</div>
</div>
<div className="content grid gap-3 h-max p-8 box-border items-center justify-between sm:grid-cols-1 sm:overflow-auto xl:overflow-hidden xl:grid-cols-[30%_70%]">
<div className="params flex flex-col gap-3.5">
{Object.entries(recycleParams).map((el, index) => {
return (
<div className="param flex flex-row justify-between items-center">
<div className="flex flex-row gap-2">
{el[1].icon}
<span className="font-normal text-sm">
{el[1].label}:
</span>
</div>
<div className="flex flex-row gap-1.5 items-center">
<span className="text-sm font-normal">
{el[1].value}
</span>
<span className="text-sm">{el[1].suffix}</span>
</div>
</div>
);
})}
</div>
<div>
<div className="h-72 w-[32rem]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
width={500}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<CartesianGrid
stroke="#374151"
strokeDasharray="0"
vertical={false} // خط‌های عمودی (از محور X) بمونه
// horizontal={false} // خط‌های افقی (از محور Y) حذف میشه
/>
<XAxis
dataKey="name"
axisLine={false} // خط اصلی محور X محو میشه
tickLine={false}
tick={{
fill: "#fff",
fontSize: 14,
fontWeight: "normal",
dy: 10, // جابجایی عمودی (مثبت = پایین‌تر، منفی = بالاتر)
}}
/>
<YAxis
type="number"
domain={[0, 100]}
axisLine={false}
tickLine={false}
tick={{
fill: "#99a1af", // رنگ متن
fontSize: 14, // سایز فونت
// fontWeight: "bold", // ضخامت
dx: -30, // جابجایی افقی (اعداد نزدیک‌تر یا دورتر از محور)
// dy:-1
}}
tickFormatter={(val) => `${val}%`}
/>
<Bar
dataKey="pv"
fill="#3AEA83"
radius={[20, 20, 0, 0]}
barSize={14}
label={{
position: "top",
fill: "#fff",
fontWeight: "bold",
}}
/>
</BarChart>
</ResponsiveContainer>
</div>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden">
<CardContent className="p-0">
<div className="border-b-2 border-gray-500/20">
<div className="flex flex-row justify-between w-full p-4 px-6">
<span>استاندارد ها و مقررات</span>
<TrendingUp />
</div>
</div>
<div className="flex flex-col gap-3 p-4 max-h-[22rem] overflow-y-scroll">
{Array.from({ length: 10 }, (index) => {
return (
<div key={`${index}-1`} className="flex gap-2">
<LoaderCircle
size={"18px"}
className="text-emerald-400"
/>
<span>استاندارد Iso 2005</span>
</div>
);
})}
</div>
</CardContent>
</Card>
</div>