// ...existing code... import { useCallback, useEffect, useRef, useState } from "react"; 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 jalaali from "jalaali-js"; import { ChevronDown, ChevronUp, CodeXml, FilterIcon, Handshake, RefreshCw, SquareUser, User, Users, } from "lucide-react"; import toast from "react-hot-toast"; import { Customized, Line, LineChart, ReferenceLine, ResponsiveContainer, XAxis, } from "recharts"; import apiService from "~/lib/api"; import { EventBus, formatCurrency, formatNumber } from "~/lib/utils"; import type { CalendarDate } from "~/types/util.type"; import DashboardLayout from "../layout"; interface innovationBuiltInDate { WorkflowID: number; approved_budget: string; done_date: string | null; observer: string; project_description: string; project_id: number | string; project_no: string; project_rating: string; project_status: string; start_date: string; title: string; } interface DialogInfo { WorkflowID: number; collaboration_model: string; complexity_level: string; developer_team_role: string; number_employees_involved: number | null; participants_full_name: string; project_description: string; project_id: string; project_no: string; project_rating: string; project_status: string; role_company_staff: string | null; technology_maturity_level: string; title: string; technology_params?: Array; } interface SortConfig { field: string; direction: "asc" | "desc"; } interface StateItem { id: string; title: string; percent: { value: number; description: string; }; total: { value: number; description: string; }; } interface BottleNeckItem { resolveBottleNeck: { label: string; value: string; description?: string; }; increaseCapacity: { label: string; value: string; description?: string; unit?: string; increasePercent: number; }; increaseIncome: { label: string; value: string; description?: string; increasePercent: number; unit?: string; }; } interface StatsCard { currencySaving: StateItem; investmentAmount: StateItem; } interface InnovationStats { average_project_score: number | null; count_innovation_construction_inside_projects: number; foreign_currency_saving: number; foreign_currency_saving_percent: number; high_level_technology_count: number; increased_capacity_after_innovation: number; increased_capacity_after_innovation_percent: number; increased_income_after_innovation: number; increased_income_after_innovation_percent: number; investment_amount: number; investment_amount_percent: number; resolved_bottleneck_count: number; } interface TechnologyParameter { WorkflowID: number; domestic_technology_parameter_value: string; foreign_technology_parameter_value: string; technology_parameter_title: string; } enum projectStatus { propozal = "پروپوزال", contract = "پیشنویس قرارداد", inprogress = "در حال انجام", stop = "متوقف شده", mafasa = "مرحله مفاصا", finish = "پایان یافته", } const columns = [ { key: "select", label: "", sortable: false, width: "50px" }, { key: "project_no", label: "شماره پروژه", sortable: true, width: "120px" }, { key: "title", label: "عنوان پروژه", sortable: true, width: "300px" }, { key: "project_status", label: "وضعیت پروژه", sortable: true, width: "140px", }, { key: "project_rating", label: "امتیاز پروژه", sortable: true, width: "120px", }, { key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" }, ]; const dialogChartData = [ { name: "مرحه پیدایش", value: 10 }, { name: "مرحله رشد", value: 14 }, { name: "مرحله بلوغ", value: 25 }, { name: "مرحله افول", value: 15 }, ]; export function InnovationBuiltInsidePage() { const { jy } = jalaali.toJalaali(new Date()); 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 [date, setDate] = useState({ start: `${jy}/01/01`, end: `${jy}/12/30`, }); const [tblAvarage, setTblAvarage] = useState(0); const [selectedProjects, setSelectedProjects] = useState>(); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [selectedProjectDetails, setSelectedProjectDetails] = useState(); const [sustainabilityStats, setSustainabilityStats] = useState({ currencySaving: { id: "reduce-pollution", title: "صرفه جویی ارزی", total: { value: 10.45, description: "میلیون ریال کاهش یافته", }, percent: { value: 10, description: "درصد نسبت به کل", }, }, investmentAmount: { id: "reduce-junkfull", title: "میزان سرمایه گذاری ", total: { value: 10, description: "میلیون ریال", }, percent: { value: 10, description: "درصد به کل", }, }, }); const [bottleNeck, setBottleNeck] = useState({ resolveBottleNeck: { label: "تعدادگلوگاه رفع شده", value: "0", description: "", }, increaseCapacity: { label: "ظرفیت تولید اضافه شده", value: "0", description: "درصد افزایش ظرفیت تولید", increasePercent: 0, unit: "تن", }, increaseIncome: { label: "میزان افزایش درآمد", value: "0", description: "درصد افزایش درآمد", increasePercent: 0, unit: "میلیون ریال", }, }); const [showDialogItems, setShowDialogItems] = useState(false); const [countOfHighTech, setCountOfHighTech] = useState(0); const observerRef = useRef(null); const fetchingRef = useRef(false); const handleSelectProject = (projectNo: string | number) => { const newSelected = new Set(selectedProjects); if (newSelected.has(projectNo)) { newSelected.delete(projectNo); } else { newSelected.add(projectNo); } setSelectedProjects(newSelected); }; const handleProjectDetails = async (project: DialogInfo) => { setShowDialogItems(true); setDetailsDialogOpen(true); setSelectedProjectDetails(project); await fetchDialogTbl(project.WorkflowID); setTimeout(() => { setShowDialogItems(false); calculateProgressBar(+project.project_rating); }, 500); }; // ...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", "complexity_level", "collaboration_model", "developer_team_role", "role_company_staff", "number_employees_involved", "participants_full_name", "technology_maturity_level", ], Sorts: [[sortConfig.field, sortConfig.direction]], Conditions: [ ["type_of_innovation", "=", "نوآوری ساخت داخل", "and"], ["start_date", ">=", date?.start || null, "and"], ["start_date", "<=", date?.end || null], ], 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 fetchDialogTbl = async (tblId: number) => { try { const response = await apiService.select({ ProcessName: "technology_parameter", OutputFields: [ "technology_parameter_title", "domestic_technology_parameter_value", "foreign_technology_parameter_value", ], Conditions: [["project_id", "=", tblId]], }); if (response.state === 0) { const dataString = response.data; if (dataString && typeof dataString === "string") { const parsedData = JSON.parse(dataString); setSelectedProjectDetails((prev: any) => ({ ...prev, technology_params: parsedData, })); } } } catch (error) { console.error("Error fetching projects:", error); toast.error("خطا در دریافت اطلاعات پروژه‌ها"); } }; const calculateProgressBar = (val: number) => { const pointer = document.getElementsByClassName( "progressBarPointer" )[0] as HTMLElement; if (!pointer) return; const leftValue = val !== 0 ? `calc(${val}% - 100px)` : `calc(0% - 40px)`; pointer.style.left = leftValue; }; const loadMore = useCallback(() => { if (hasMore && !loading) { setCurrentPage((prev) => prev + 1); } }, [hasMore, loading]); useEffect(() => { EventBus.on("dateSelected", (date: CalendarDate) => { if (date) { setDate(date); } }); }, []); useEffect(() => { fetchProjects(true); }, [sortConfig, date]); useEffect(() => { fetchStats(); }, [selectedProjects, date]); 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 fetchStats = async () => { try { detailsDialogOpen; setStatsLoading(true); const raw = await apiService.call({ innovation_construction_inside_function: { project_ids: selectedProjects && selectedProjects?.size > 0 ? Array.from(selectedProjects).join(" , ") : "", start_date: date?.start || null, end_date: date?.end || null, }, }); let payload: any = raw?.data; if (typeof payload === "string") { try { payload = JSON.parse(payload); } catch {} } const parseNum = (v: unknown): any => { 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 data: Array = JSON.parse( payload?.innovation_construction_inside_function ); const stats = data[0]; const normalized: any = { currencySaving: { value: formatNumber(parseNum(stats?.foreign_currency_saving)), percent: formatNumber( parseNum(stats?.foreign_currency_saving_percent) ), }, investmentAmount: { value: formatNumber(parseNum(stats?.investment_amount)), percent: formatNumber(parseNum(stats?.investment_amount_percent)), }, technology: { value: formatNumber(parseNum(stats?.high_level_technology_count)), }, income: { value: formatNumber( parseNum(stats.increased_income_after_innovation) ), percent: formatNumber( parseNum(stats.increased_income_after_innovation_percent) ), }, capacity: { value: formatNumber( parseNum(stats.increased_capacity_after_innovation) ), percent: formatNumber( parseNum(stats.increased_capacity_after_innovation_percent) ), }, resolveBottleNeck: { value: formatNumber(parseNum(stats.resolved_bottleneck_count)), }, countOfHighTech: formatNumber(stats.high_level_technology_count), avarage: stats.average_project_score, countInnovationGreenProjects: stats.count_innovation_construction_inside_projects, }; 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, currencySaving: { ...prev.currencySaving, total: { ...prev.currencySaving.total, value: normalized.currencySaving.value, }, percent: { ...prev.currencySaving.percent, value: normalized.currencySaving.percent, }, }, investmentAmount: { ...prev.investmentAmount, total: { ...prev.investmentAmount.total, value: normalized.investmentAmount.value, }, percent: { ...prev.investmentAmount.percent, value: normalized.investmentAmount.percent, }, }, })); setBottleNeck((prev) => ({ ...prev, increaseIncome: { ...prev.increaseIncome, value: normalized.income.value, increasePercent: normalized.income.percent, }, increaseCapacity: { ...prev.increaseCapacity, value: normalized.capacity.value, increasePercent: normalized.capacity.percent, }, resolveBottleNeck: { ...prev.resolveBottleNeck, value: normalized.resolveBottleNeck.value, }, // average: normalized.avarage, // countInnovationGreenProjects: normalized.countInnovationGreenProjects, })); setCountOfHighTech(normalized.countOfHighTech); }; const renderCellContent = (item: innovationBuiltInDate, column: any) => { const value = item[column.key as keyof innovationBuiltInDate]; 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; }; return (
{/* Stats Cards */}
{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}
))} {statsLoading ? (
{[1, 2, 3].map((i) => (
))}
) : (

بر طرف کردن گلوگاه

{Object.entries(bottleNeck).map(([key, value]) => { return (
{value.value} {value.unit && ( {value.unit} )}
{value.label}
{value.description && value.description} {value.increasePercent}
); })}
)} {statsLoading ? (
) : ( تعداد فناوری سطح بالا {countOfHighTech} )}
{/* Data Table */}
{columns.map((column) => ( {column.sortable ? ( ) : ( column.label )} ))} {loading ? ( // Skeleton loading rows (compact) Array.from({ length: 40 }).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(actualTotalCount)}
میانگین :‌
{formatNumber( ((tblAvarage ?? 0) as number).toFixed?.(1) ?? 0 )}
*/}
{/* Project Details Dialog */} شرح پروژه
{showDialogItems ? (
) : (

{selectedProjectDetails?.title}

{selectedProjectDetails?.project_description || "-"}

)}

دانش فنی محصول جدید

{showDialogItems ? (
سطح تکنولوژی
) : (
سطح پایین
سطح متوسط
سطح بالا
سطح تکنولوژی
)}

مشارکت در پروژه

{showDialogItems ? (
{[...Array(4)].map((_, i) => (
))}
) : (
مدل همکاری: {selectedProjectDetails?.collaboration_model}
نقش تیم توسعه دهنده: {selectedProjectDetails?.developer_team_role}
نقش کارکنان شرکت: {selectedProjectDetails?.role_company_staff ?? "-"}
تعداد کارکنان درگیر: {selectedProjectDetails?.number_employees_involved ?? 0}
لیست کارکنان : {selectedProjectDetails?.participants_full_name}
)}
{/* Project Details */}
{showDialogItems ? (
{[...Array(5)].map((_, rowIndex) => (
))}
) : (
شاخص مقایسه با نمونه خارجی نمونه داخلی نمونه خارجی
{selectedProjectDetails?.technology_params?.map( (el, index) => { return (
{el.technology_parameter_title}
{formatNumber( el.domestic_technology_parameter_value )} میلیون لیتر
{formatNumber( el.foreign_technology_parameter_value )} میلیون لیتر
); } )}
)}
{showDialogItems ? (
{[...Array(4)].map((_, i) => (
))} {[...Array(5)].map((_, i) => (
))}
) : ( { const { xAxisMap, width, height } = props; const xAxes = Object.values(xAxisMap || {}); if (!xAxes.length) return null; const xAxis: any = xAxes[0]; const ticks = xAxis?.ticks || []; const value = selectedProjectDetails?.technology_maturity_level; const xFromScale = typeof xAxis?.scale === "function" ? xAxis.scale(value) : undefined; const tick = ticks.find( (t: any) => t && (t.value === value || (t.payload && t.payload.value === value)) ); const axisOffsetX = xAxis?.x ?? 0; const x = (xFromScale ?? tick?.coordinate ?? width / 2) + axisOffsetX - 15; const rectWidth = 130; const rectHeight = 28; const rectX = x - rectWidth / 2; const axisHeight = xAxis?.height ?? 40; const rectY = height - axisHeight - 30; return ( سطح بلوغ تکنولوژی ); }} /> )}
); } export default InnovationBuiltInsidePage;