import { useState, useEffect, useCallback, useRef } from "react"; import { DashboardLayout } from "../layout"; import { Card, CardContent } from "~/components/ui/card"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Checkbox } from "~/components/ui/checkbox"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { ChevronUp, ChevronDown, RefreshCw, ExternalLink, } from "lucide-react"; import apiService from "~/lib/api"; import toast from "react-hot-toast"; import {Funnel, Wrench , CirclePause , DollarSign} from "lucide-react" interface ProcessInnovationData { project_no: string; title: string; project_status: string; project_rating: string; reduce_prevention_production_stops: string; throat_removal: string; amount_currency_reduction: string; Reduce_rate_failure: string; } interface SortConfig { field: string; direction: "asc" | "desc"; } interface StatsCard { id: string; title: string; value: string; description: string; icon: React.ReactNode; color: string; } interface InnovationStats { totalProjects: number; averageScore: number; productionStopsPreventionSum: number; // مجموع جلوگیری از توقفات تولید bottleneckRemovalCount: number; // تعداد رفع گلوگاه currencyReductionSum: number; // مجموع کاهش ارز بری (میلیون ریال) frequentFailuresReductionSum: number; // مجموع کاهش خرابی های پرتکرار percentProductionStops: number; // درصد مقایسه‌ای جلوگیری از توقفات تولید percentBottleneckRemoval: number; // درصد مقایسه‌ای رفع گلوگاه percentCurrencyReduction: number; // درصد مقایسه‌ای کاهش ارز بری percentFailuresReduction: number; // درصد مقایسه‌ای کاهش خرابی‌های پرتکرار } 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 ProcessInnovationPage() { 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({ totalProjects: 0, averageScore: 0, productionStopsPreventionSum: 0, bottleneckRemovalCount: 0, currencyReductionSum: 0, frequentFailuresReductionSum: 0, percentProductionStops: 0, percentBottleneckRemoval: 0, percentCurrencyReduction: 0, percentFailuresReduction: 0, }); const [sortConfig, setSortConfig] = useState({ field: "start_date", direction: "asc", }); const [selectedProjects, setSelectedProjects] = useState>(new Set()); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [selectedProjectDetails, setSelectedProjectDetails] = useState(null); const observerRef = useRef(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)) { newSelected.delete(projectNo); } else { newSelected.add(projectNo); } setSelectedProjects(newSelected); }; const handleProjectDetails = (project: ProcessInnovationData) => { setSelectedProjectDetails(project); setDetailsDialogOpen(true); }; const formatNumber = (value: string | number) => { if (!value) return "0"; const numericValue = typeof value === "string" ? parseFloat(value) : value; if (isNaN(numericValue)) return "0"; return new Intl.NumberFormat("fa-IR").format(numericValue); }; // Stats cards data - computed from projects data const statsCards: StatsCard[] = [ { id: "production-stops-prevention", title: "جلوگیری از توقفات تولید", value: formatNumber(stats.productionStopsPreventionSum.toFixed?.(1) ?? stats.productionStopsPreventionSum), description: "ظرفیت افزایش یافته", icon: , color: "text-emerald-400", }, { id: "bottleneck-removal", title: "رفع گلوگاه", value: formatNumber(stats.bottleneckRemovalCount), description: "تعداد رفع گلوگاه", icon: , color: "text-emerald-400" }, { id: "currency-reduction", title: "کاهش ارز بری", value: formatNumber(stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum), description: "میلیون ریال کاهش یافته", icon: , color: "text-emerald-400", }, { id: "frequent-failures-reduction", title: "کاهش خرابیهای پرتکرار", value: formatNumber(stats.frequentFailuresReductionSum.toFixed?.(1) ?? stats.frequentFailuresReductionSum), description: "مجموع درصد کاهش خرابی", icon: , color: "text-emerald-400", } ]; const fetchProjects = async (reset = false) => { if (fetchingRef.current) { return; } try { fetchingRef.current = true; if (reset) { setLoading(true); setCurrentPage(1); } else { setLoadingMore(true); } const pageToFetch = reset ? 1 : currentPage; const response = await apiService.select({ ProcessName: "project", OutputFields: [ "project_no", "title", "project_status", "project_rating", "reduce_prevention_production_stops", "throat_removal", "amount_currency_reduction", "Reduce_rate_failure", ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, Sorts: [[sortConfig.field, sortConfig.direction]], 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)) { 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 (!loadingMore && hasMore && !loading) { setCurrentPage((prev) => prev + 1); } }, [loadingMore, hasMore, loading]); useEffect(() => { fetchProjects(true); fetchTotalCount(); fetchStats(); }, [sortConfig]); useEffect(() => { if (currentPage > 1) { fetchProjects(false); } }, [currentPage]); useEffect(() => { const scrollContainer = document.querySelector('.overflow-auto'); const handleScroll = () => { if (!scrollContainer || !hasMore || loadingMore) return; const { scrollTop, scrollHeight, clientHeight } = scrollContainer; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; if (scrollPercentage >= 0.9) { loadMore(); } }; if (scrollContainer) { scrollContainer.addEventListener('scroll', handleScroll); } return () => { if (scrollContainer) { scrollContainer.removeEventListener('scroll', handleScroll); } }; }, [loadMore, hasMore, loadingMore]); 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; setActualTotalCount(count); // 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.callInnovationProcess({ innovation_process_function: { }, }); let payload: any = raw?.data; if (typeof payload === "string") { try { payload = JSON.parse(payload); } catch {} } const parseNum = (v: unknown): number => { if (v == null) return 0; if (typeof v === "number") return v; if (typeof v === "string") { const cleaned = v.replace(/,/g, "").trim(); const n = parseFloat(cleaned); return isNaN(n) ? 0 : n; } return 0; }; const normalized: InnovationStats = { totalProjects: parseNum(payload?.count_innovation_process_projects), averageScore: parseNum(payload?.average_project_score), productionStopsPreventionSum: parseNum(payload?.sum_stopping_production), bottleneckRemovalCount: parseNum(payload?.count_throat_removal), currencyReductionSum: parseNum(payload?.sum_reduction_value_currency), frequentFailuresReductionSum: parseNum(payload?.sum_reducing_breakdowns), percentProductionStops: parseNum(payload?.percent_sum_stopping_production), percentBottleneckRemoval: parseNum(payload?.percent_throat_removal), percentCurrencyReduction: parseNum(payload?.percent_reduction_value_currency), percentFailuresReduction: parseNum(payload?.percent_reducing_breakdowns), }; setStats(normalized); } catch (error) { console.error("Error fetching stats:", error); } finally { setStatsLoading(false); } }; const handleRefresh = () => { fetchingRef.current = false; setCurrentPage(1); setProjects([]); setHasMore(true); fetchProjects(true); fetchTotalCount(); fetchStats(); }; const formatCurrency = (amount: string | number) => { if (!amount) return "0 ریال"; const numericAmount = typeof amount === "string" ? parseFloat(amount.replace(/,/g, "")) : amount; if (isNaN(numericAmount)) return "0 ریال"; return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال"; }; const formatPercentage = (value: string | number) => { if (!value) return "0%"; const numericValue = typeof value === "string" ? parseFloat(value) : value; if (isNaN(numericValue)) return "0%"; return `${numericValue.toFixed(1)}%`; }; const getStatusColor = (status: string) => { switch (status?.toLowerCase()) { case "فعال": return "#3AEA83"; case "متوقف": return "#F76276"; case "تکمیل شده": return "#32CD32"; default: return "#6B7280"; } }; const getRatingColor = (rating: string) => { const ratingNum = parseFloat(rating); if (isNaN(ratingNum)) return "#6B7280"; if (ratingNum >= 8) return "#3AEA83"; if (ratingNum >= 6) return "#69C8EA"; if (ratingNum >= 4) return "#FFD700"; return "#F76276"; }; const renderCellContent = (item: ProcessInnovationData, column: any) => { const value = item[column.key as keyof ProcessInnovationData]; switch (column.key) { case "select": return ( handleSelectProject(item.project_no)} className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600" /> ); 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) || "-"}; } }; return (
{/* Stats Cards */}
{/* Stats Grid */}
{loading || statsLoading ? ( // Loading skeleton for stats cards - matching new design Array.from({ length: 4 }).map((_, index) => (
)) ) : ( statsCards.map((card) => (

{card.title}

{card.icon}

{card.value}

{card.description}

)) )}
{/* Process Impacts Chart */}

تاثیرات فرآیندی به صورت درصد مقایسه ای

{/* Chart Container */}
{/* Chart Item 1 */}
کاهش توقفات تولید
{formatNumber(((stats.percentProductionStops ?? 0) as number).toFixed?.(1) ?? (stats.percentProductionStops ?? 0))}%
{/* Chart Item 2 */}
رفع گلوگاه تولید
{formatNumber(((stats.percentBottleneckRemoval ?? 0) as number).toFixed?.(1) ?? (stats.percentBottleneckRemoval ?? 0))}%
{/* Chart Item 3 */}
کاهش ارز بری
{formatNumber(((stats.percentCurrencyReduction ?? 0) as number).toFixed?.(0) ?? (stats.percentCurrencyReduction ?? 0))}%
{/* Chart Item 4 */}
کاهش خرابی پر تکرار
{formatNumber(((stats.percentFailuresReduction ?? 0) as number).toFixed?.(1) ?? (stats.percentFailuresReduction ?? 0))}%
{/* Percentage Scale */}
۰٪ { (() => { const p1 = (stats.percentProductionStops || 0); const p2 = (stats.percentBottleneckRemoval || 0); const p3 = (stats.percentCurrencyReduction || 0); const p4 = (stats.percentFailuresReduction || 0); const maxVal = Math.max(p1, p2, p3, p4); return ( <> {formatNumber(Math.round(maxVal / 4))}٪ {formatNumber(Math.round(maxVal / 2))}٪ {formatNumber(Math.round((maxVal * 3) / 4))}٪ {formatNumber(Math.round(maxVal))}٪ ); })() }
{/* Data Table */}
{columns.map((column) => ( {column.key === "select" ? (
0} onCheckedChange={handleSelectAll} className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600" />
) : column.sortable ? ( ) : ( column.label )}
))}
{loading ? ( // Skeleton loading rows 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 && (
)}
{/* Selection Summary */} {/* {selectedProjects.size > 0 && (
{selectedProjects.size} پروژه انتخاب شده
)} */} {/* Footer */}
کل پروژه ها : {formatNumber(stats.totalProjects || actualTotalCount)}
{/* Project number column - empty */}
{/* Title column - empty */}
{/* Project status column - empty */}
{/* Project rating column - show average */}
میانگین امتیاز :‌
{formatNumber(((stats.averageScore ?? 0) as number).toFixed?.(1) ?? (stats.averageScore ?? 0))}
{/* Details column - show total count */}
{/* Project Details Dialog */} جزئیات پروژه {selectedProjectDetails && (
{/* Project Header */}

{selectedProjectDetails.title}

{selectedProjectDetails.project_no} {selectedProjectDetails.project_status}
{/* Project Metrics */}

امتیاز پروژه

{formatNumber(selectedProjectDetails.project_rating)}

کاهش توقفات تولید

{formatNumber(selectedProjectDetails.reduce_prevention_production_stops)}

رفع گلوگاه تولید

{formatNumber(selectedProjectDetails.throat_removal)}

کاهش ارز بری

{formatCurrency(selectedProjectDetails.amount_currency_reduction)}

کاهش خرابی پر تکرار

{formatNumber(selectedProjectDetails.Reduce_rate_failure)}
{/* Action Buttons */}
)}
); }