feat/green-innovation #6
|
|
@ -21,19 +21,34 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "~/components/ui/dialog";
|
} from "~/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
ChevronUp,
|
BarChart,
|
||||||
ChevronDown,
|
Bar,
|
||||||
RefreshCw,
|
XAxis,
|
||||||
ExternalLink,
|
YAxis,
|
||||||
Building2,
|
CartesianGrid,
|
||||||
PickaxeIcon,
|
Tooltip,
|
||||||
UserIcon,
|
ResponsiveContainer,
|
||||||
UsersIcon,
|
LineChart,
|
||||||
} from "lucide-react";
|
Line,
|
||||||
|
Rectangle,
|
||||||
|
Legend,
|
||||||
|
} from "recharts";
|
||||||
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react";
|
import {
|
||||||
import ProjectDetail from "../projects/project-detail";
|
LoaderCircle,
|
||||||
|
TrendingUp,
|
||||||
|
Key,
|
||||||
|
Sparkle,
|
||||||
|
Zap,
|
||||||
|
Flame,
|
||||||
|
Building2,
|
||||||
|
PickaxeIcon,
|
||||||
|
UsersIcon,
|
||||||
|
UserIcon,
|
||||||
|
RefreshCw,
|
||||||
|
} from "lucide-react";
|
||||||
import DashboardLayout from "../layout";
|
import DashboardLayout from "../layout";
|
||||||
|
|
||||||
moment.loadPersian({ usePersianDigits: true });
|
moment.loadPersian({ usePersianDigits: true });
|
||||||
|
|
@ -80,6 +95,19 @@ interface InnovationStats {
|
||||||
percentFailuresReduction: number; // درصد مقایسهای کاهش خرابیهای پرتکرار
|
percentFailuresReduction: number; // درصد مقایسهای کاهش خرابیهای پرتکرار
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Params {
|
||||||
|
icon: any;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
suffix: string;
|
||||||
|
}
|
||||||
|
interface RecycleParams {
|
||||||
|
water: Params;
|
||||||
|
food: Params;
|
||||||
|
power: Params;
|
||||||
|
oil: Params;
|
||||||
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ key: "select", label: "", sortable: false, width: "50px" },
|
{ key: "select", label: "", sortable: false, width: "50px" },
|
||||||
{ key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" },
|
{ key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" },
|
||||||
|
|
@ -131,6 +159,32 @@ export function GreenInnovationPage() {
|
||||||
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
||||||
const [selectedProjectDetails, setSelectedProjectDetails] =
|
const [selectedProjectDetails, setSelectedProjectDetails] =
|
||||||
useState<ProcessInnovationData | null>(null);
|
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 observerRef = useRef<HTMLDivElement>(null);
|
||||||
const fetchingRef = useRef(false);
|
const fetchingRef = useRef(false);
|
||||||
|
|
||||||
|
|
@ -579,109 +633,204 @@ 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 (
|
return (
|
||||||
<DashboardLayout title="نوآوری سبز">
|
<DashboardLayout title="نوآوری سبز">
|
||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="flex gap-6">
|
<div className="flex gap-6 mb-6">
|
||||||
<div className="space-y-6 w-full">
|
<div className="flex flex-col gap-11 w-full h-full">
|
||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<div className="flex flex-col gap-3">
|
{loading || statsLoading
|
||||||
{loading || statsLoading
|
? // Loading skeleton for stats cards - matching new design
|
||||||
? // Loading skeleton for stats cards - matching new design
|
Array.from({ length: 2 }).map((_, index) => (
|
||||||
Array.from({ length: 2 }).map((_, index) => (
|
<Card
|
||||||
<Card
|
key={`skeleton-${index}`}
|
||||||
key={`skeleton-${index}`}
|
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden"
|
||||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden"
|
>
|
||||||
>
|
<CardContent className="p-2">
|
||||||
<CardContent className="p-2">
|
<div className="flex flex-col justify-between gap-2 h-full">
|
||||||
<div className="flex flex-col justify-between gap-2">
|
<div className="flex justify-between items-center border-b-2 border-gray-500/20">
|
||||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20">
|
<div
|
||||||
<div
|
className="h-6 bg-gray-600 rounded animate-pulse"
|
||||||
className="h-6 bg-gray-600 rounded animate-pulse"
|
style={{ width: "60%" }}
|
||||||
style={{ width: "60%" }}
|
/>
|
||||||
/>
|
<div className="p-3 bg-emerald-500/20 rounded-full w-fit">
|
||||||
<div className="p-3 bg-emerald-500/20 rounded-full w-fit">
|
<div className="w-6 h-6 bg-gray-600 rounded animate-pulse" />
|
||||||
<div className="w-6 h-6 bg-gray-600 rounded animate-pulse" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-center flex-col p-1">
|
|
||||||
<div
|
|
||||||
className="h-8 bg-gray-600 rounded mb-1 animate-pulse"
|
|
||||||
style={{ width: "40%" }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="h-4 bg-gray-600 rounded animate-pulse"
|
|
||||||
style={{ width: "80%" }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
<div className="flex items-center justify-center flex-col p-1">
|
||||||
</Card>
|
<div
|
||||||
))
|
className="h-8 bg-gray-600 rounded mb-1 animate-pulse"
|
||||||
: statsCards.map((card) => (
|
style={{ width: "40%" }}
|
||||||
<Card
|
/>
|
||||||
key={card.id}
|
<div
|
||||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
|
className="h-4 bg-gray-600 rounded animate-pulse"
|
||||||
>
|
style={{ width: "80%" }}
|
||||||
<CardContent className="p-0">
|
/>
|
||||||
<div className="flex flex-col justify-between gap-2">
|
</div>
|
||||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 ">
|
</div>
|
||||||
<h3 className="text-lg font-bold text-white font-persian p-4">
|
</CardContent>
|
||||||
{card.title}
|
</Card>
|
||||||
</h3>
|
))
|
||||||
|
: statsCards.map((card) => (
|
||||||
|
<Card
|
||||||
|
key={card.id}
|
||||||
|
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] 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">
|
||||||
|
{card.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-emerald-400 mb-1">
|
||||||
|
% {card.percent.value}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-gray-400 font-persian">
|
||||||
|
{card.percent.description}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between p-6">
|
<b className="block w-0.5 h-12 bg-gray-600 rotate-45" />
|
||||||
<span className="text-emerald-400">
|
<div className="flex flex-col">
|
||||||
|
<span className="text-3xl font-bold text-emerald-400 mb-1">
|
||||||
{card.total.value}
|
{card.total.value}
|
||||||
</span>
|
</span>
|
||||||
<b className="block w-0.5 h-8 bg-emerald-400 rotate-45" />
|
<span className="text-sm text-gray-400 font-persian">
|
||||||
<span className="text-emerald-400">
|
{card.total.description}
|
||||||
{card.percent.value}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</CardContent>
|
||||||
))}
|
</Card>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 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-0 h-full overflow-hidden">
|
||||||
<CustomBarChart
|
<div className="border-b-2 border-gray-500/20">
|
||||||
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
|
<div className="w-full p-4 px-6">
|
||||||
loading={statsLoading}
|
<span>بازیافت و بازیابی منابع</span>
|
||||||
data={[
|
</div>
|
||||||
{
|
</div>
|
||||||
label: "کاهش توقفات تولید",
|
<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%]">
|
||||||
value: stats.percentProductionStops || 0,
|
<div className="params flex flex-col gap-3.5">
|
||||||
color: "bg-emerald-400",
|
{Object.entries(recycleParams).map((el, index) => {
|
||||||
labelColor: "text-white",
|
return (
|
||||||
},
|
<div className="param flex flex-row justify-between items-center">
|
||||||
{
|
<div className="flex flex-row gap-2">
|
||||||
label: "رفع گلوگاه تولید",
|
{el[1].icon}
|
||||||
value: stats.percentBottleneckRemoval || 0,
|
<span className="font-normal text-sm">
|
||||||
color: "bg-emerald-400",
|
{el[1].label}:
|
||||||
labelColor: "text-white",
|
</span>
|
||||||
},
|
</div>
|
||||||
{
|
<div className="flex flex-row gap-1.5 items-center">
|
||||||
label: "کاهش ارز بری",
|
<span className="text-sm font-normal">
|
||||||
value: stats.percentCurrencyReduction || 0,
|
{el[1].value}
|
||||||
color: "bg-emerald-400",
|
</span>
|
||||||
labelColor: "text-white",
|
<span className="text-sm">{el[1].suffix}</span>
|
||||||
},
|
</div>
|
||||||
{
|
</div>
|
||||||
label: "کاهش خرابی پر تکرار",
|
);
|
||||||
value: stats.percentFailuresReduction || 0,
|
})}
|
||||||
color: "bg-emerald-400",
|
</div>
|
||||||
labelColor: "text-white",
|
|
||||||
},
|
<div>
|
||||||
]}
|
<div className="h-72 w-[32rem]">
|
||||||
barHeight="h-5"
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
showAxisLabels={true}
|
<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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user