// import moment from "moment-jalaali"; import { useCallback, useEffect, useRef, useState } from "react"; import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis, } from "recharts"; import { Badge } from "~/components/ui/badge"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { Checkbox } from "~/components/ui/checkbox"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { formatNumber } from "~/lib/utils"; import { Building2, ChevronDown, ChevronUp, Flame, Key, LoaderCircle, PickaxeIcon, RefreshCw, Sparkle, TrendingUp, UserIcon, UsersIcon, Zap, } from "lucide-react"; import toast from "react-hot-toast"; import apiService from "~/lib/api"; import { formatCurrency } from "~/lib/utils"; import DashboardLayout from "../layout"; // moment.loadPersian({ usePersianDigits: true }); interface GreenInnovationData { WorkflowID: string; approved_budget: string; done_date: string | null; observer: string; project_description: string; project_id: string; project_no: string; project_rating: string; project_status: string; start_date: string; title: string; } interface SortConfig { field: string; direction: "asc" | "desc"; } interface StateItem { id: string; title: string; percent: { value: number; description: string; }; total: { value: number; description: string; }; } interface StatsCard { pollution: StateItem; waste: StateItem; } interface InnovationStats { electricity_recovery_reduction: number; electricity_recovery_reduction_percent: number; feed_recovery_reduction: number; feed_recovery_reduction_percent: number; fuel_recovery_reduction: number; fuel_recovery_reduction_percent: number; pollution_reduction: number; pollution_reduction_percent: number; waste_reduction: number; waste_reductionn_percent: number; water_recovery_reduction: number; water_recovery_reduction_percent: number; average_project_score: number; count_innovation_green_projects: number; standard_regulations: string; } interface Params { icon: any; label: string; value: number; suffix: string; percent: number; } interface RecycleParams { water: Params; food: Params; power: Params; oil: Params; } interface stateCounter { totalProjects: number; } interface ChartDataItem { name: string; pv: any; // actual value amt: number; // max value or target } enum projectStatus { propozal = "پروپوزال", contract = "پیشنویس قرارداد", inprogress = "در حال انجام", stop = "متوقف شده", mafasa = "مرحله مفاصا", finish = "پایان یافته", } const columns = [ { key: "select", label: "", sortable: false, width: "50px" }, { key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" }, { key: "title", label: "عنوان پروژه", sortable: true, width: "400px" }, { key: "project_status", label: "وضعیت پروژه", sortable: true, width: "140px", }, { key: "project_rating", label: "امتیاز پروژه", sortable: true, width: "140px", }, { key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" }, ]; export function GreenInnovationPage() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [pageSize] = useState(20); const [hasMore, setHasMore] = useState(true); const [totalCount, setTotalCount] = useState(0); const [actualTotalCount, setActualTotalCount] = useState(0); const [statsLoading, setStatsLoading] = useState(false); const [stats, setStats] = useState(); const [sortConfig, setSortConfig] = useState({ field: "start_date", direction: "asc", }); const [tblAvarage, setTblAvarage] = useState(0); const [selectedProjects, setSelectedProjects] = useState>( new Set() ); const [standartRegulation, setStandardRegulation] = useState>( [] ); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [selectedProjectDetails, setSelectedProjectDetails] = useState(null); const [recycleParams, setRecycleParams] = useState({ water: { icon: , label: "آب", value: 0, suffix: "لیتر", percent: 0, }, food: { icon: , label: "خوراک", value: 0, suffix: "تن", percent: 0, }, power: { icon: , label: "برق", value: 0, suffix: "میلیون مگاوات", percent: 0, }, oil: { icon: , label: "سوخت", value: 0, suffix: "متر مربع", percent: 0, }, }); const [sustainabilityStats, setSustainabilityStats] = useState({ pollution: { id: "reduce-pollution", title: "کاهش آلایندگی", total: { value: 10.45, description: "میلیون ریال", }, percent: { value: 10, description: "درصد به کل درآمد", }, }, waste: { id: "reduce-junkfull", title: "کاهش ضایعات", total: { value: 10, description: "میلیون ریال", }, percent: { value: 10, description: "درصد به کل درآمد", }, }, }); const observerRef = useRef(null); const fetchingRef = useRef(false); const handleSelectProject = (projectNo: string) => { const newSelected = new Set(selectedProjects); if (newSelected.has(projectNo)) { newSelected.delete(projectNo); } else { newSelected.add(projectNo); } setSelectedProjects(newSelected); }; const handleProjectDetails = (project: GreenInnovationData) => { setSelectedProjectDetails(project); setDetailsDialogOpen(true); }; // ...existing code... const fetchProjects = async (reset = false) => { if (fetchingRef.current) { return; } try { fetchingRef.current = true; if (reset) { setCurrentPage(1); } else { setLoadingMore(true); } const pageToFetch = reset ? 1 : currentPage; const response = await apiService.select({ ProcessName: "project", OutputFields: [ "project_id", "project_no", "title", "project_status", "project_rating", "project_description", "start_date", "done_date", "approved_budget", "observer", ], Sorts: [[sortConfig.field, sortConfig.direction]], Conditions: [["type_of_innovation", "=", "نوآوری سبز"]], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); if (response.state === 0) { const dataString = response.data; if (dataString && typeof dataString === "string") { try { const parsedData = JSON.parse(dataString); if (Array.isArray(parsedData)) { if (reset) { setProjects(parsedData); setTotalCount(parsedData.length); } else { setProjects((prev) => [...prev, ...parsedData]); setTotalCount((prev) => prev + parsedData.length); } setHasMore(parsedData.length === pageSize); } else { if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } catch (parseError) { console.error("Error parsing project data:", parseError); if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } else { if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } else { toast.error(response.message || "خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } catch (error) { console.error("Error fetching projects:", error); toast.error("خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } finally { setLoading(false); setLoadingMore(false); fetchingRef.current = false; } }; const loadMore = useCallback(() => { if (hasMore && !loading) { setCurrentPage((prev) => prev + 1); } }, [hasMore, loading]); useEffect(() => { fetchProjects(true); fetchTotalCount(); }, [sortConfig]); useEffect(() => { fetchStats(); }, [selectedProjects]); useEffect(() => { if (currentPage > 1) { fetchProjects(false); } }, [currentPage]); useEffect(() => { const scrollContainer = document.querySelector(".overflow-auto"); const handleScroll = () => { if (!scrollContainer || !hasMore) return; const { scrollTop, scrollHeight, clientHeight } = scrollContainer; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; if (scrollPercentage == 1) { loadMore(); } }; if (scrollContainer) { scrollContainer.addEventListener("scroll", handleScroll); } return () => { if (scrollContainer) { scrollContainer.removeEventListener("scroll", handleScroll); } }; }, [loadMore, hasMore, loadingMore]); useEffect(() => { setLoading(true); }, []); const handleSort = (field: string) => { fetchingRef.current = false; setSortConfig((prev) => ({ field, direction: prev.field === field && prev.direction === "asc" ? "desc" : "asc", })); setCurrentPage(1); setProjects([]); setHasMore(true); }; const fetchTotalCount = async () => { try { const response = await apiService.select({ ProcessName: "project", OutputFields: ["count(project_no)"], Conditions: [["type_of_innovation", "=", "نوآوری سبز"]], }); if (response.state === 0) { const dataString = response.data; if (dataString && typeof dataString === "string") { try { const parsedData = JSON.parse(dataString); if (Array.isArray(parsedData) && parsedData[0]) { const count = parsedData[0].project_no_count || 0; // Keep stats in sync if backend stats not yet loaded setStats((prev) => ({ ...prev, totalProjects: count })); } } catch (parseError) { console.error("Error parsing count data:", parseError); } } } } catch (error) { console.error("Error fetching total count:", error); } }; // Fetch aggregated stats from backend call API (innovation_process_function) const fetchStats = async () => { try { setStatsLoading(true); const raw = await apiService.call({ innovation_green_function: { project_ids: selectedProjects.size > 0 ? Array.from(selectedProjects).join(" , ") : "", }, }); let payload: any = raw?.data; if (typeof payload === "string") { try { payload = JSON.parse(payload); } catch {} } const parseNum = (v: unknown): any => { const convertNumber = typeof v === "number" ? Math.max(0, v) : 0; if (v == null) return 0; if (typeof v === "number") return convertNumber; if (typeof v === "string") { const cleaned = v.replace(/,/g, "").trim(); const n = parseFloat(cleaned); return isNaN(n) ? 0 : convertNumber; } return 0; }; const data: Array = JSON.parse( payload?.innovation_green_function ); const stats = data[0]; const normalized: any = { food: { value: formatNumber(parseNum(stats?.feed_recovery_reduction)), percent: parseNum(stats?.feed_recovery_reduction_percent), }, oil: { value: formatNumber(parseNum(stats?.fuel_recovery_reduction)), percent: parseNum(stats?.fuel_recovery_reduction_percent), }, power: { value: formatNumber(parseNum(stats?.electricity_recovery_reduction)), percent: parseNum(stats?.electricity_recovery_reduction_percent), }, water: { value: formatNumber(parseNum(stats.water_recovery_reduction)), percent: parseNum(stats.water_recovery_reduction_percent), }, pollution: { value: formatNumber(parseNum(stats.pollution_reduction)), percent: formatNumber(parseNum(stats.pollution_reduction_percent)), }, waste: { value: formatNumber(parseNum(stats.waste_reduction)), percent: formatNumber(parseNum(stats.waste_reductionn_percent)), }, avarage: stats.average_project_score, countInnovationGreenProjects: stats.count_innovation_green_projects, standardRegulation: stats.standard_regulations .replace("\r", "") .split("\n"), }; setStandardRegulation(normalized.standardRegulation); setActualTotalCount(normalized.countInnovationGreenProjects); setTblAvarage(normalized.avarage); setPageData(normalized); } catch (error) { console.error("Error fetching stats:", error); } finally { setStatsLoading(false); } }; const setPageData = (normalized: any) => { setSustainabilityStats((prev) => ({ ...prev, pollution: { ...prev.pollution, total: { ...prev.pollution.total, value: normalized.pollution.value }, percent: { ...prev.pollution.percent, value: normalized.pollution.percent, }, }, waste: { ...prev.waste, total: { ...prev.waste.total, value: normalized.waste.value }, percent: { ...prev.waste.percent, value: normalized.waste.percent }, }, })); setRecycleParams((prev) => ({ ...prev, water: { ...prev.water, value: normalized.water.value, percent: normalized.water.percent, }, food: { ...prev.food, value: normalized.food.value, percent: normalized.food.percent, }, oil: { ...prev.oil, value: normalized.oil.value, percent: normalized.oil.percent, }, power: { ...prev.power, value: normalized.power.value, percent: normalized.power.percent, }, })); setChartData([ { name: recycleParams.water.label, pv: Math.max(0, normalized.water.percent).toFixed(2), amt: 100, }, { name: recycleParams.power.label, pv: Math.max(0, normalized.power.percent).toFixed(2), amt: 100, }, { name: recycleParams.oil.label, pv: Math.max(0, normalized.oil.percent).toFixed(2), amt: 100, }, { name: recycleParams.food.label, pv: Math.max(0, normalized.food.percent).toFixed(2), amt: 100, }, ]); }; const renderCellContent = (item: GreenInnovationData, column: any) => { const value = item[column.key as keyof GreenInnovationData]; switch (column.key) { case "select": return ( handleSelectProject(item.project_id)} className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600 cursor-pointer" /> ); case "details": return ( ); case "amount_currency_reduction": return ( {formatCurrency(String(value))} ); case "project_no": return ( {String(value)} ); case "title": return {String(value)}; case "project_status": return (
{String(value)}
); case "project_rating": return ( {formatNumber(String(value))} ); case "reduce_prevention_production_stops": case "throat_removal": case "Reduce_rate_failure": return ( {formatNumber(String(value))} ); default: return {String(value) || "-"}; } }; const statusColor = (status: projectStatus): any => { let el = null; switch (status) { case projectStatus.contract: el = "teal"; break; case projectStatus.finish: el = "info"; break; case projectStatus.stop: el = "warning"; break; case projectStatus.inprogress: el = "teal"; break; case projectStatus.mafasa: el = "destructive"; break; case projectStatus.propozal: el = "info"; } return el; }; const [chartData, setChartData] = useState>([ { 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 (
{/* Stats Cards */}
{loading || statsLoading ? // Loading skeleton for stats cards - matching new design Array.from({ length: 2 }).map((_, index) => (
)) : Object.entries(sustainabilityStats).map(([key, value]) => (

{value.title}

% {value.percent?.value} {value.percent?.description}
{value.total?.value} {value.total?.description}
))}
{/* Process Impacts Chart */} {statsLoading ? (
{[...Array(3)].map((_, paramIndex) => (
))}
{[...Array(8)].map((_, barIndex) => (
))}
) : (
بازیافت و بازیابی منابع
{Object.entries(recycleParams).map((el, index) => { return (
{el[1].icon} {el[1].label}:
{el[1].value} {el[1].suffix}
); })}
`${formatNumber(val)}%`} /> `${formatNumber(value)}%`, }} />
)}
{statsLoading ? ( <> ) : ( <> استاندارد ها و مقررات )}
{statsLoading ? Array.from({ length: 10 }).map((_, index) => (
)) : standartRegulation.map((item, index) => (
{item}
))}
{/* Data Table */}
{columns.map((column) => ( {column.sortable ? ( ) : ( column.label )} ))} {loading ? ( // Skeleton loading rows (compact) Array.from({ length: 10 }).map((_, index) => ( {columns.map((column) => (
))} )) ) : projects.length === 0 ? ( هیچ پروژه‌ای یافت نشد ) : ( projects.map((project, index) => ( {columns.map((column) => ( {renderCellContent(project, column)} ))} )) )}
{/* Infinite scroll trigger */}
{loadingMore && (
)}
کل پروژه ها :{formatNumber(actualTotalCount)}
میانگین :‌
{formatNumber( ((tblAvarage ?? 0) as number).toFixed?.(1) ?? 0 )}
{/*
{" "} کل پروژه ها :{" "} {formatNumber(stats?.totalProjects || actualTotalCount)}
{" "} میانگین امتیاز :‌
{formatNumber( ((tblAvarage ?? 0) as number).toFixed?.(1) ?? tblAvarage ?? 0 )}
*/}
{/* Project Details Dialog */} شرح پروژه
{/* Project Description */}

{selectedProjectDetails?.title}

{selectedProjectDetails?.project_description || "-"}

{/* Project Details */}
جزئیات پروژه

زمان شروع:

{selectedProjectDetails?.start_date ? moment( selectedProjectDetails?.start_date, "YYYY-MM-DD" ).format("YYYY/MM/DD") : "-"}

زمان پایان:

{selectedProjectDetails?.done_date ? moment( selectedProjectDetails?.done_date, "YYYY-MM-DD" ).format("YYYY/MM/DD") : "-"}

هزینه برآورد شده:

{formatNumber( Number( selectedProjectDetails?.approved_budget.replaceAll( ",", "" ) ) ) || "-"}

نفر مرتبط:

{selectedProjectDetails?.observer || "-"}
); } export default GreenInnovationPage;