inogen/app/components/dashboard/project-management/product-innovation-page.tsx
2025-09-11 07:32:37 +03:30

1116 lines
43 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
ArrowDownCircle,
ArrowUpCircle,
Building2,
ChevronDown,
ChevronUp,
CirclePause,
DollarSign,
Funnel,
Loader2,
PickaxeIcon,
RefreshCw,
TrendingUp,
UserIcon,
UsersIcon,
Wrench,
} from "lucide-react";
import moment from "moment-jalaali";
import { useCallback, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Checkbox } from "~/components/ui/checkbox";
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
import { FunnelChart } from "~/components/ui/funnel-chart";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import { Label } from "~/components/ui/label";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import apiService from "~/lib/api";
import { formatNumber } from "~/lib/utils";
import { DashboardLayout } from "../layout";
import { Skeleton } from "~/components/ui/skeleton";
moment.loadPersian({ usePersianDigits: true });
interface ProjectData {
project_no: string;
project_id: string;
title: string;
project_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;
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 function ProductInnovationPage() {
const [projects, setProjects] = useState<ProductInnovationData[]>([]);
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<ProductInnovationStats>({
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<SortConfig>({
field: "start_date",
direction: "asc",
});
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
const [selectedProjectDetails, setSelectedProjectDetails] =
useState<ProductInnovationData | null>(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<any[]>([]);
const [allExportData, setAllExportData] = useState<any[]>([]);
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 observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false);
const handleProjectDetails = async (project: ProductInnovationData) => {
setSelectedProjectDetails(project);
setDetailsDialogOpen(true);
await fetchPopupData(project.project_id);
};
const fetchPopupData = async (projectId: string) => {
try {
setPopupLoading(true);
// Fetch popup stats
const statsResponse = await apiService.call({
innovation_product_popup_function1: {
project_id: projectId
}
});
if (statsResponse.state === 0) {
const statsData = JSON.parse(statsResponse.data);
if (statsData.innovation_product_popup_function1 && statsData.innovation_product_popup_function1[0]) {
setPopupStats(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
setAllExportData(chartData);
// Filter data for the selected project (bar chart)
const filteredData = chartData.filter(item =>
item.product_title === selectedProjectDetails?.title
);
setExportChartData(filteredData);
}
}
} catch (error) {
console.error("Error fetching popup data:", error);
} finally {
setPopupLoading(false);
}
};
const loadMore = useCallback(() => {
if (!loadingMore && hasMore && !loading) {
setCurrentPage((prev) => prev + 1);
}
}, [loadingMore, 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",
"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", "=", "نوآوری در محصول"]],
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<any>({
innovation_product_function: {},
});
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<any> = 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: formatNumber(normalized.new_products_revenue_share),
percent: formatNumber(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(() => {
fetchProjects(true);
}, [sortConfig]);
useEffect(() => {
fetchStats();
}, []);
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 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))].sort();
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 ? Math.round(productData.export_revenue_sum / 1000000) : 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 (
<Button
variant="ghost"
size="sm"
onClick={() => handleProjectDetails(item)}
className="text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/20 p-2 h-auto"
>
جزئیات بیشتر
</Button>
);
case "project_no":
return (
<Badge variant="outline" className="font-mono">
{String(value)}
</Badge>
);
case "title":
return <span className="font-medium text-white">{String(value)}</span>;
case "project_status":
return (
<div className="flex items-center gap-1">
<Badge
variant={statusColor(value as projectStatus)}
className="font-medium border-2 p-0 block w-2 h-2 rounded-full"
style={{
border: "none",
}}
></Badge>
{String(value)}
</div>
);
case "project_rating":
return (
<Badge
variant="outline"
className={`text-lg text-center border-none mx-auto`}
>
{formatNumber(String(value))}
</Badge>
);
default:
return <span className="text-gray-300">{String(value) || "-"}</span>;
}
};
return (
<DashboardLayout title="نوآوری در محصول">
<div className="p-6 space-y-4 flex justify-center gap-4">
{/* Stats Cards */}
<div className="flex flex-col gap-6">
<div className="space-y-6 w-full">
{/* Stats Grid */}
<div className="grid grid-cols-2 grid-rows-2 gap-5 h-full">
{loading || statsLoading
? // Loading skeleton for stats cards - matching new design
Array.from({ length: 3 }).map((_, index) => (
<Card
key={`skeleton-${index}`}
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden [&>*:first-child]:row-span-1"
>
<CardContent className="p-2">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
<div
className="h-6 bg-gray-600 rounded animate-pulse"
style={{ width: "60%" }}
/>
<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>
</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>
</CardContent>
</Card>
))
: Object.entries(stateCard).map(([key, card] , index) => (
<Card
key={card.id}
className={`bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50 ${index !== 0 ? "row-start-2 " : "col-span-2"} `}
>
<CardContent className="p-2 h-full">
<div className="grid grid-cols-2 justify-between gap-2 h-full">
<div className="flex justify-between rows-start-1 col-span-2 items-center border-b-2 mx-4 border-gray-500/20">
<h3 className="text-lg text-white font-persian py-2">
{card.title}
</h3>
<div
className={`p-3 gird placeitems-center rounded-full w-fit `}
>
</div>
</div>
<div className={`flex items-center row-start-2 justify-center flex-col row-start-2 p-1 my-auto ${card?.percent ? "col-span-1 col-start-1" : "col-span-2"}`}>
<p
className={`text-3xl font-bold ${card.color} mb-1`}
>
{card.value}
</p>
<p className="text-sm text-gray-300 font-persian">
{card.description}
</p>
</div>
{card?.percent && <span className="text-gray-600 row-start-2 col-span-2 self-center col-start-2 font-thin text-5xl">/</span>}
{card?.percent && <div className="flex col-span-1 items-center row-start-2 my-auto col-start-2 justify-center flex-col p-1 my-auto">
<p
className={`text-3xl font-bold ${card.color} mb-1`}
>
{card?.percent}
</p>
<p className="text-sm text-gray-300 font-persian">
{card.descriptionPercent}
</p>
</div>}
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* Funnel Chart */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] h-full backdrop-blur-sm rounded-2xl w-full overflow-hidden">
<CardContent className="p-6">
<FunnelChart
title="قيف فرآیند پروژه ها"
data={[
{
name: "تعداد کل",
value: stats.all_funnel,
label: "تعداد کل",
},
{
name: "نمونه موفق",
value: stats.successful_sample_funnel,
label: "نمونه موفق",
},
{
name: "محصولات موفق",
value: stats.successful_products_funnel,
label: "محصولات موفق",
},
{
name: "بهبود یا تغییر موفق",
value: stats.successful_improvement_or_change_funnel,
label: "بهبود یا تغییر موفق",
},
{
name: "محصول جدید",
value: stats.new_product_funnel,
label: "محصول جدید",
},
]}
/>
</CardContent>
</Card>
</div>
{/* Data Table */}
<Card className="bg-transparent rounded-2xl overflow-hidden">
<CardContent className="p-0">
<div className="relative">
<Table containerClassName="overflow-auto custom-scrollbar backdrop max-h-[calc(100vh-200px)]">
<TableHeader>
<TableRow className="bg-[#3F415A]">
{columns.map((column) => (
<TableHead
key={column.key}
className="text-center font-persian whitespace-nowrap text-gray-200 font-medium sticky top-0 z-20 bg-[#3F415A]"
style={{ width: column.width }}
>
{column.sortable ? (
<button
onClick={() => handleSort(column.key)}
className="flex items-center gap-2"
>
<span>{column.label}</span>
{sortConfig.field === column.key ? (
sortConfig.direction === "asc" ? (
<ChevronUp className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)
) : (
<div className="w-4 h-4" />
)}
</button>
) : (
column.label
)}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
// Skeleton loading rows (compact)
Array.from({ length: 10 }).map((_, index) => (
<TableRow
key={`skeleton-${index}`}
className="text-sm leading-tight h-8"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-right whitespace-nowrap border-emerald-500/20 py-1 px-2"
>
<div className="flex items-center gap-2">
<div className="w-2.5 h-2.5 bg-gray-600 rounded-full animate-pulse" />
<div
className="h-2.5 bg-gray-600 rounded animate-pulse"
style={{ width: `${Math.random() * 60 + 40}%` }}
/>
</div>
</TableCell>
))}
</TableRow>
))
) : projects.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-8"
>
<span className="text-gray-400 font-persian">
هیچ پروژهای یافت نشد
</span>
</TableCell>
</TableRow>
) : (
projects.map((project, index) => (
<TableRow
key={`${project.project_no}-${index}`}
className="text-sm leading-tight h-8"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-center whitespace-nowrap border-emerald-500/20 py-1 px-2"
>
{renderCellContent(project, column)}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* Infinite scroll trigger */}
<div ref={observerRef} className="h-auto">
{loadingMore && (
<div className="flex items-center justify-center py-1">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 animate-spin text-emerald-400" />
<span className="font-persian text-gray-300 text-xs"></span>
</div>
</div>
)}
</div>
</CardContent>
{/* Footer */}
<div className="p-2 px-4 bg-[#3F415A]">
<div className="flex gap-4 text-sm text-gray-300 font-persian justify-between sm:flex-col xl:flex-row">
<div className="text-center gap-2 items-center xl:w-1/3 pr-36 sm:w-full">
<div className="text-base text-gray-401 mb-1">
کل پروژه ها :{formatNumber(stats?.count_innovation_construction_inside_projects)}
</div>
</div>
<div className="flex items-center flex-row gap-20 status justify-center xl:w-2/3 sm:w-full">
<div className="flex flex-row-reverse ml-[-1rem]">
<span className="block w-7 h-2.5 bg-violet-500 rounded-tl-xl rounded-bl-xl"></span>
<span className="block w-7 h-2.5 bg-purple-500 "></span>
<span className="block w-7 h-2.5 bg-cyan-300 "></span>
<span className="block w-7 h-2.5 bg-pink-400 rounded-tr-xl rounded-br-xl"></span>
</div>
<div className="flex justify-center items-center gap-2">
<div className="text-base text-gray-400 mb-1">میانگین :</div>
<div className="font-bold">
{formatNumber(
((stats.average_project_score ?? 0) as number).toFixed?.(1) ?? 0
)}
</div>
</div>
</div>
</div>
</div>
</Card>
</div>
{/* Project Details Dialog */}
<Dialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
<DialogContent className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] max-w-7xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-white mr-4 border-b-2 border-gray-600 pb-4 font-persian text-right">
جزئیات پروژه
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 p-6">
{/* Left Column - Project Description and Charts */}
<div className="lg:col-span-2 space-y-6">
{/* Project Description */}
<div className="bg-gray-800/50 rounded-lg p-6">
<h2 className="text-2xl font-bold text-white mb-4">
{selectedProjectDetails?.title}
</h2>
<p className="text-gray-300 font-persian leading-relaxed">
{selectedProjectDetails?.project_description || "-"}
</p>
{/* Project Timeline */}
<div className="mt-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm text-gray-400">۱۴۰۴</span>
<span className="text-sm text-gray-400">۱۴۰۵</span>
<span className="text-sm text-gray-400">۱۴۰۶</span>
<span className="text-sm text-gray-400">۱۴۰۷</span>
</div>
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">ثبت ایده</span>
</div>
<div className="flex-1 h-1 bg-gray-600"></div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">تحلیل بازار</span>
</div>
<div className="flex-1 h-1 bg-gray-600"></div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-gray-600 rounded-full"></div>
<span className="text-sm text-gray-400">توسعه</span>
</div>
<div className="flex-1 h-1 bg-gray-600"></div>
<div className="flex items-center space-x-2">
<div className="w-4 h-4 bg-gray-600 rounded-full"></div>
<span className="text-sm text-gray-400">تجاری سازی</span>
</div>
</div>
<div className="text-xs text-emerald-400 mt-2">وضعیت فعلی: تحلیل بازار</div>
</div>
</div>
{/* Export Revenue Bar Chart */}
<div className="bg-gray-800/50 rounded-lg p-6">
<h3 className="text-lg font-semibold text-white mb-4">ظرفیت صادر شده</h3>
<div className="h-64">
{popupLoading ? (
<div className="flex items-center justify-center h-full">
<div className="animate-pulse text-gray-400">در حال بارگذاری...</div>
</div>
) : exportChartData.length > 0 ? (
<CustomBarChart
title=""
loading={false}
data={exportChartData
.sort((a, b) => {
// Sort by season order
const seasonOrder = ['بهار', 'تابستان', 'پاییز', 'زمستان'];
const getSeasonIndex = (season: string) => {
const year = season.split(' ')[1];
const seasonName = season.split(' ')[0];
return parseInt(year) * 4 + seasonOrder.indexOf(seasonName);
};
return getSeasonIndex(a.full_season) - getSeasonIndex(b.full_season);
})
.map(item => ({
label: item.full_season,
value: Math.round(item.export_revenue_sum / 1000000), // Convert to millions
color: "bg-emerald-400",
labelColor: "text-white"
}))}
barHeight="h-6"
showAxisLabels={true}
/>
) : (
<div className="flex items-center justify-center h-full text-gray-400">
دادهای برای نمایش وجود ندارد
</div>
)}
</div>
</div>
{/* Export Revenue Line Chart */}
<div className="bg-gray-800/50 rounded-lg p-6">
<h3 className="text-lg font-semibold text-white mb-4">ظرفیت صادر شده</h3>
<div className="h-64">
{popupLoading ? (
<div className="flex items-center justify-center h-full">
<div className="animate-pulse text-gray-400">در حال بارگذاری...</div>
</div>
) : allExportData.length > 0 ? (
<ResponsiveContainer width="100%" height="100%">
<LineChart data={transformDataForLineChart(allExportData)}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis
dataKey="season"
stroke="#9CA3AF"
fontSize={12}
/>
<YAxis
stroke="#9CA3AF"
fontSize={12}
domain={[0, 1000]}
/>
<Tooltip
contentStyle={{
backgroundColor: '#1F2937',
border: '1px solid #374151',
borderRadius: '8px',
color: '#F9FAFB'
}}
/>
<Legend />
{[...new Set(allExportData.map(item => item.product_title))].slice(0, 5).map((product, index) => {
const colors = ['#10B981', '#EF4444', '#3B82F6', '#F59E0B', '#8B5CF6'];
return (
<Line
key={product}
type="monotone"
dataKey={product}
stroke={colors[index % colors.length]}
strokeWidth={2}
dot={{ fill: colors[index % colors.length], strokeWidth: 2, r: 4 }}
name={`محصول ${index + 1}`}
/>
);
})}
</LineChart>
</ResponsiveContainer>
) : (
<div className="flex items-center justify-center h-full text-gray-400">
دادهای برای نمایش وجود ندارد
</div>
)}
</div>
</div>
</div>
{/* Right Column - Stats Cards and Details */}
<div className="space-y-6">
{/* Stats Cards */}
<div className="space-y-4">
<div className="bg-gray-800/50 rounded-lg p-4">
<h3 className="text-sm text-gray-400 mb-2">میزان صادارت محصول جدید</h3>
{popupLoading ? (
<div className="animate-pulse">
<div className="h-8 bg-gray-600 rounded mb-2"></div>
<div className="h-4 bg-gray-600 rounded mb-2"></div>
<div className="h-6 bg-gray-600 rounded"></div>
</div>
) : (
<>
<div className="text-2xl font-bold text-emerald-400 mb-1">
{formatNumber(popupStats.new_products_export)}
</div>
<div className="text-sm text-gray-300">میلیون ریال</div>
<div className="text-lg font-bold text-red-400 mt-2">
{formatNumber(popupStats.new_products_export_percent)}%
</div>
<div className="text-xs text-gray-400">درصد به کل صادرات</div>
</>
)}
</div>
<div className="bg-gray-800/50 rounded-lg p-4">
<h3 className="text-sm text-gray-400 mb-2">تاثیر در واردات</h3>
{popupLoading ? (
<div className="animate-pulse">
<div className="h-8 bg-gray-600 rounded mb-2"></div>
<div className="h-4 bg-gray-600 rounded mb-2"></div>
<div className="h-6 bg-gray-600 rounded"></div>
</div>
) : (
<>
<div className="text-2xl font-bold text-emerald-400 mb-1">
{formatNumber(popupStats.import_impact)}
</div>
<div className="text-sm text-gray-300">میلیون ریال</div>
<div className="text-lg font-bold text-red-400 mt-2">
{formatNumber(popupStats.import_impact_percent)}%
</div>
<div className="text-xs text-gray-400">درصد صرفه جویی</div>
</>
)}
</div>
</div>
{/* Technical Knowledge */}
<div className="bg-gray-800/50 rounded-lg p-4">
<h3 className="text-sm text-gray-400 mb-4">دانش فنی محصول جدید</h3>
<div className="space-y-3">
<div className="flex items-center space-x-3">
<Checkbox
checked={false}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
<span className="text-sm text-white">توسعه درونزا</span>
</div>
<div className="flex items-center space-x-3">
<Checkbox
checked={true}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
<span className="text-sm text-white">همکاری فناورانه</span>
</div>
<div className="flex items-center space-x-3">
<Checkbox
checked={true}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
<span className="text-sm text-white">سایر</span>
</div>
</div>
</div>
{/* Standards */}
<div className="bg-gray-800/50 rounded-lg p-4">
<h3 className="text-sm text-gray-400 mb-4">استانداردهای ملی و بین المللی اخذ شده</h3>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">استاندارد ملی شماره یک</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">استاندارد بین المللی شماره یک</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">استاندارد ملی شماره یک</span>
</div>
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">استاندارد ملی شماره یک</span>
</div>
</div>
</div>
{/* Knowledge-based Certificate Button */}
<div className="bg-red-600/20 border border-red-600 rounded-lg p-4 text-center">
<button className="text-red-400 font-medium">
گواهی دانش بنیان ندارد
</button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</DashboardLayout>
);
}