import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import toast from "react-hot-toast"; import { Bar, BarChart, LabelList } from "recharts"; import { Badge } from "~/components/ui/badge"; import { BaseCard } from "~/components/ui/base-card"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { Checkbox } from "~/components/ui/checkbox"; import { MetricCard } from "~/components/ui/metric-card"; import { Popover, PopoverContent, PopoverTrigger, } from "~/components/ui/popover"; import jalaali from "jalaali-js"; import { CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from "recharts"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; import { FunnelChart } from "~/components/ui/funnel-chart"; import { Skeleton } from "~/components/ui/skeleton"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { Tooltip as TooltipSh, TooltipTrigger } from "~/components/ui/tooltip"; import apiService from "~/lib/api"; import { EventBus, formatNumber, handleDataValue } from "~/lib/utils"; import type { CalendarDate } from "~/types/util.type"; import { DashboardLayout } from "../layout"; interface ProjectData { project_no: string; project_id: string; title: string; project_status: string; current_status?: string; project_rating: string; project_description: string; developed_technology_type: string; obtained_standard_title: string; knowledge_based_certificate_obtained: string; knowledge_based_certificate_number: string; certificate_obtain_date: string; issuing_authority: string; } interface ProductInnovationStats { new_products_revenue_share: number; new_products_revenue_share_percent: number; new_products_export: number; import_impact: number; all_funnel: number; successful_sample_funnel: number; successful_products_funnel: number; successful_improvement_or_change_funnel: number; new_product_funnel: number; count_innovation_construction_inside_projects: number; average_project_score: number; } interface ProductInnovationData { WorkflowID: number; ValueP1215S1887ValueID: number; ValueP1215S1887StageID: number; project_id: string; project_no: string; title: string; project_status: projectStatus; project_rating: string; current_status?: string; project_description: string; developed_technology_type: string; obtained_standard_title: string; knowledge_based_certificate_obtained: string; knowledge_based_certificate_number: string; certificate_obtain_date: string; issuing_authority: string; } interface SortConfig { field: string; direction: "asc" | "desc"; } enum projectStatus { propozal = "پروپوزال", contract = "پیشنویس قرارداد", inprogress = "در حال انجام", stop = "متوقف شده", mafasa = "مرحله مفاصا", finish = "پایان یافته", notstarted = "شروع نشده", delayed = "تأخیر دارد", } const columns = [ { 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 default function Timeline(valueTimeLine: string) { const stages = ["تجاری سازی", "توسعه", "تحلیل بازار", "ثبت ایده"]; const currentStage = stages ?.toReversed() ?.findIndex((x: string) => x == valueTimeLine); const per = () => { const main = stages?.findIndex((x) => x == "ثبت ایده"); console.log("yay ", 25 * main + 12.5); return 25 * main + 12.5; }; return (
{/* Year labels */}
۱۴۰۷ ۱۴۰۶ ۱۴۰۵ ۱۴۰۴
{/* Timeline bar */}
{stages.map((stage, index) => (
{stage}
))} {/* Vertical line showing current position */} {valueTimeLine?.length > 0 && ( <> {" "}
وضعیت فعلی
)}
); } export function ProductInnovationPage() { // const [showPopup, setShowPopup] = useState(false); 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 [statsLoading, setStatsLoading] = useState(false); const [stats, setStats] = useState({ new_products_revenue_share: 0, new_products_revenue_share_percent: 0, new_products_export: 0, import_impact: 0, all_funnel: 0, successful_sample_funnel: 0, successful_products_funnel: 0, successful_improvement_or_change_funnel: 0, new_product_funnel: 0, count_innovation_construction_inside_projects: 0, average_project_score: 0, }); const [sortConfig, setSortConfig] = useState({ field: "start_date", direction: "asc", }); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); const [selectedProjectDetails, setSelectedProjectDetails] = useState(null); const [popupStats, setPopupStats] = useState({ new_products_export: 0, new_products_export_percent: 0, import_impact: 0, import_impact_percent: 0, }); const [exportChartData, setExportChartData] = useState([]); const [allExportData, setAllExportData] = useState([]); const [popupLoading, setPopupLoading] = useState(false); const [stateCard, setStateCard] = useState({ revenueNewProducts: { id: "revenueNewProducts", title: "سهم از درآمد برای محصولات جدید", value: 0, description: "میلیون ریال", descriptionPercent: "درصد به کل درآمد", color: "text-[#3AEA83]", percent: 0, }, newProductExports: { id: "newProductExports", title: "صادرات محصول جدید", value: "0", description: "میلیون ریال", color: "text-[#3AEA83]", }, impactOnImports: { id: "impactOnImports", title: "تأثیر در واردات", value: "0", description: "میلیون ریال", color: "text-[#F76276]", }, }); const [date, setDate] = useState({ start: `${jy}/01/01`, end: `${jy}/12/30`, }); const observerRef = useRef(null); const fetchingRef = useRef(false); const handleProjectDetails = async (project: ProductInnovationData) => { setSelectedProjectDetails(project); setDetailsDialogOpen(true); await fetchPopupData(project, date?.start, date?.end); }; const fetchPopupData = async ( project: ProductInnovationData, startDate?: string, endDate?: string ) => { try { setPopupLoading(true); // Fetch popup stats const statsResponse = await apiService.call({ innovation_product_popup_function1: { project_id: project.project_id, start_date: startDate || null, end_date: endDate || null, }, }); if (statsResponse.state === 0) { const statsData = JSON.parse(statsResponse.data); if ( statsData.innovation_product_popup_function1 && statsData.innovation_product_popup_function1[0] ) { setPopupStats( JSON.parse(statsData.innovation_product_popup_function1)[0] ); } } // Fetch export chart data const chartResponse = await apiService.select({ ProcessName: "export_product_innovation", OutputFields: ["product_title", "full_season", "sum(export_revenue)"], GroupBy: ["product_title", "full_season"], }); if (chartResponse.state === 0) { const chartData = JSON.parse(chartResponse.data); if (Array.isArray(chartData)) { // Set all data for line chart // Filter data for the selected project (bar chart) const filteredData = chartData.filter( (item) => item.product_title === project?.title ); setAllExportData(chartData); setExportChartData(filteredData); } } } catch (error) { console.error("Error fetching popup data:", error); } finally { setPopupLoading(false); } }; const loadMore = useCallback(() => { if (hasMore && !loading) { setCurrentPage((prev) => prev + 1); } }, [hasMore, loading]); 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_id", "project_no", "title", "project_status", "current_status", "project_rating", "project_description", "developed_technology_type", "obtained_standard_title", "knowledge_based_certificate_obtained", "knowledge_based_certificate_number", "certificate_obtain_date", "issuing_authority", ], Sorts: [["start_date", "asc"]], 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 fetchStats = async () => { try { setStatsLoading(true); const raw = await apiService.call({ innovation_product_function: { start_date: date?.start || null, end_date: date?.end || null, }, }); let payload: any = JSON.parse(raw?.data); 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_product_function); const stats = data[0]; const normalized: ProductInnovationStats = { new_products_revenue_share: parseNum(stats?.new_products_revenue_share), new_products_revenue_share_percent: parseNum( stats?.new_products_revenue_share_percent ), import_impact: parseNum(stats?.import_impact), new_products_export: parseNum(stats?.new_products_export), all_funnel: parseNum(stats?.all_funnel), successful_sample_funnel: parseNum(stats?.successful_sample_funnel), successful_products_funnel: parseNum(stats?.successful_products_funnel), successful_improvement_or_change_funnel: parseNum( stats?.successful_improvement_or_change_funnel ), new_product_funnel: parseNum(stats?.new_product_funnel), count_innovation_construction_inside_projects: parseNum( stats?.count_innovation_construction_inside_projects ), average_project_score: parseNum(stats?.average_project_score), }; setStateCard((prev) => ({ ...prev, revenueNewProducts: { ...prev.revenueNewProducts, value: normalized.new_products_revenue_share, percent: normalized.new_products_revenue_share_percent, }, impactOnImports: { ...prev.impactOnImports, value: formatNumber(normalized.import_impact), }, newProductExports: { ...prev.newProductExports, value: formatNumber(normalized.new_products_export), }, })); setStats(normalized); } catch (error) { console.error("Error fetching stats:", error); } finally { setStatsLoading(false); } }; useEffect(() => { EventBus.on("dateSelected", (date: CalendarDate) => { if (date) { setDate(date); } }); }, []); useEffect(() => { fetchProjects(true); }, [sortConfig, date]); useEffect(() => { fetchStats(); }, [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]); 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 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) + " ریال"; // }; // Transform data for line chart const transformDataForLineChart = (data: any[]) => { const seasons = [...new Set(data.map((item) => item.full_season))]; const products = [...new Set(data.map((item) => item.product_title))]; return seasons.map((season) => { const seasonData: any = { season }; products.forEach((product) => { const productData = data.find( (item) => item.product_title === product && item.full_season === season ); seasonData[product] = productData?.export_revenue_sum > 0 && productData ? Math.round(productData?.export_revenue_sum) : 0; }); return seasonData; }); }; // const getRatingColor = (rating: string | number) => { // const numRating = typeof rating === "string" ? parseInt(rating) : rating; // if (numRating >= 150) return "text-emerald-400"; // if (numRating >= 100) return "text-blue-400"; // return "text-red-400"; // }; 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"; break; case projectStatus.notstarted: el = "secondary"; break; case projectStatus.delayed: el = "destructive"; break; } return el; }; const renderCellContent = (item: ProductInnovationData, column: any) => { const value = item[column.key as keyof ProductInnovationData]; switch (column.key) { case "select": return null; case "details": return ( ); case "project_no": return ( {String(value)} ); case "title": return ( {String(value)} ); case "project_status": return (
{String(value)}
); case "project_rating": return ( {formatNumber(String(value))} ); default: return ( {String(value) || "-"} ); } }; const seasonOrder = ["بهار", "تابستان", "پاییز", "زمستان"]; const sortedBarData = exportChartData .sort((a, b) => { const getSeasonIndex = (s: string) => { const [seasonName, year] = s.split(" "); return parseInt(year) * 4 + seasonOrder.indexOf(seasonName); }; return getSeasonIndex(a.full_season) - getSeasonIndex(b.full_season); }) .map((item) => ({ label: item.full_season, value: item.export_revenue_sum < 0 ? 0 : Math.round(item.export_revenue_sum), })); return (
{/* Stats Cards */}
{/* Stats Grid */}
{loading || statsLoading ? ( // Loading skeleton for stats cards - matching new design Array.from({ length: 3 }).map((_, index) => (
)) ) : ( <> {/* First card (Metric/Matrix style) - span two columns */}
{/* Second card */}

{stateCard.newProductExports.value}

{stateCard.newProductExports.description}
{/* Third card - basic BaseCard */}

{stateCard.impactOnImports.value}

{stateCard.impactOnImports.description}
)}
{/* Funnel Chart */}
{/* 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 && (
)}
{/* Footer */}
کل پروژه ها : {formatNumber( stats?.count_innovation_construction_inside_projects )}
میانگین :‌
{formatNumber( ((stats.average_project_score ?? 0) as number).toFixed?.( 1 ) ?? 0 )}
{/* Project Details Dialog */} شرح پروژه
{/* right Column - Stats Cards and Details */}
{/* Stats Cards */}

{selectedProjectDetails?.title}

{selectedProjectDetails?.project_description}

{/* Technical Knowledge */}

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

توسعه درونزا
همکاری فناورانه
سایر
{/* Standards */}

استانداردهای ملی و بین‌المللی اخذ شده

{selectedProjectDetails?.obtained_standard_title && selectedProjectDetails?.obtained_standard_title.length > 0 ? (
{(Array.isArray( selectedProjectDetails?.obtained_standard_title ) ? selectedProjectDetails?.obtained_standard_title : [selectedProjectDetails?.obtained_standard_title] ).map((standard, index) => (
{standard}
))}
) : (

هیچ استانداردی ثبت نشده است.

)}
{/* Knowledge-based Certificate Button */}
{selectedProjectDetails?.knowledge_based_certificate_obtained === "خیر" ? (
) : (

شماره گواهی: {selectedProjectDetails?.knowledge_based_certificate_number || "—"}

تاریخ اخذ: {handleDataValue( selectedProjectDetails?.certificate_obtain_date ) || "—"}

مرجع صادرکننده:{" "} {selectedProjectDetails?.issuing_authority || "—"}

)}
{/* Left Column - Project Description and Charts */} {popupLoading ? (
) : (
{/* Project Description - two MetricCards side by side */}
0 ? popupStats?.new_products_export : 0 )} percentValue={Math.round( popupStats?.new_products_export_percent > 0 ? popupStats?.new_products_export_percent : 0 )} valueLabel="میلیون ریال" percentLabel="درصد به کل صادرات" /> 0 ? popupStats?.import_impact : 0 )} percentValue={Math.round( popupStats?.import_impact_percent > 0 ? popupStats?.import_impact_percent : 0 )} valueLabel="میلیون ریال" percentLabel="درصد صرفه جویی" />
{/* Export Revenue Bar Chart */}

ظرفیت صادر شده

{exportChartData.length > 0 ? ( `${value.split(" ")[0]} ${formatNumber(value.split(" ")[1]).replaceAll("٬", "")}` } fontSize={11} /> `${formatNumber(value)} میلیون` } /> `${formatNumber(value)}` } position="top" offset={15} fill="F9FAFB" className="fill-foreground" fontSize={16} /> ) : (
داده‌ای برای نمایش وجود ندارد
)}
{/* Export Revenue Line Chart */}

ظرفیت صادر شده

{allExportData.length > 0 ? ( ( {(payload as any).value} )} /> `${formatNumber(value)} میلیون` } /> `${formatNumber(value)} میلیون` } contentStyle={{ backgroundColor: "#1F2937", border: "1px solid #374151", borderRadius: "6px", padding: "6px 10px", fontSize: "11px", color: "#F9FAFB", }} /> {[ ...new Set( allExportData.map((item) => item.product_title) ), ] .slice(0, 5) .map((product, index) => { const colors = [ "#10B981", "#EF4444", "#3B82F6", "#F59E0B", "#8B5CF6", ]; return ( ); })} ) : (
داده‌ای برای نمایش وجود ندارد
)}
)}
); }