inogen/app/components/dashboard/project-management/product-innovation-page.tsx
saeed0920 abb8bcc9e4 product-idea (#10)
Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/10
Co-authored-by: saeed0920 <sd.eed1381@gmail.com>
Co-committed-by: saeed0920 <sd.eed1381@gmail.com>
2025-09-13 16:48:52 +03:30

1313 lines
50 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 { Bar, BarChart, LabelList } from "recharts"
import {
Popover,
PopoverTrigger,
PopoverContent,
} from "~/components/ui/popover"
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, handleDataValue } from "~/lib/utils";
import { DashboardLayout } from "../layout";
import { Skeleton } from "~/components/ui/skeleton";
import { Tooltip as TooltipSh, TooltipTrigger, TooltipContent } from "~/components/ui/tooltip";
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 default function Timeline() {
const stages = ["تجاری سازی", "توسعه", "تحلیل بازار", "ثبت ایده"];
const currentStage = 1; // index of current stage
return (
<div className="w-full p-4">
{/* Year labels */}
<div className="grid grid-cols-4 place-items-center border-b border-gray-300/40 mb-2 items-center text-slate-300 font-thin text-sm">
<span>۱۴۰۷</span>
<span>۱۴۰۶</span>
<span>۱۴۰۵</span>
<span>۱۴۰۴</span>
</div>
{/* Timeline bar */}
<div className="relative rounded-lg flex mb-4 items-center">
{stages.map((stage, index) => (
<div key={stage} className="flex-1 flex flex-col items-center relative">
<TooltipSh>
<TooltipTrigger asChild>
<div
className={`w-full py-2 text-center transition-colors duration-300 ${
index <= currentStage ? "bg-[#3D7968] text-white" : "bg-[#3AEA83] text-slate-600"
}`}
>
<span className="mt-1 text-sm">{stage}</span>
</div>
</TooltipTrigger>
</TooltipSh>
</div>
))}
{/* Vertical line showing current position */}
<div
className="absolute left-[37%] top-0 h-[150%] bottom-0 w-[2px] bg-white rounded-full"
style={{ left: `${(currentStage + 0.5) * (100 / stages.length)}%` }}
/>
<div
className="absolute top-15 h-[max-content] translate-x-[-50%] text-xs text-gray-300 border-gray-400 rounded-md border px-2 bottom-0"
style={{ left: `${(currentStage + 0.5) * (100 / stages.length)}%` }}
>وضعیت فعلی</div>
</div>
</div>
);
}
export function ProductInnovationPage() {
const [showPopup, setShowPopup] = useState(false);
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 [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);
console.log(project)
setDetailsDialogOpen(true);
await fetchPopupData(project);
};
const fetchPopupData = async (project: ProductInnovationData) => {
try {
setPopupLoading(true);
// Fetch popup stats
const statsResponse = await apiService.call({
innovation_product_popup_function1: {
project_id: project.project_id
}
});
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 (!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))];
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 (
<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>;
}
};
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 (
<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 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-right 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">
کل پروژه ها :{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">میانگین :</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-[95vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-white mr-4 border-b-2 border-gray-600 pb-2 font-persian text-right">
شرح پروژه
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 px-6 py-2">
{/* right Column - Stats Cards and Details */}
<div className="space-y-4">
{/* Stats Cards */}
<div className="space-y-4">
<h3 className="font-bold text-lg">{selectedProjectDetails?.title}</h3>
<p className="py-2">{selectedProjectDetails?.project_description}</p>
</div>
<Timeline />
{/* Technical Knowledge */}
<div className=" rounded-lg py-2 mb-0">
<h3 className="text-sm text-gray-400 mb-2">دانش فنی محصول جدید</h3>
<div className="flex gap-4 items-center">
<div className="flex items-center gap-2">
<span className="text-sm text-white">توسعه درونزا</span>
<Checkbox
checked={selectedProjectDetails?.developed_technology_type === "توسعه درونزا"}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-white">همکاری فناورانه</span>
<Checkbox
checked={selectedProjectDetails?.developed_technology_type === "همکاری فناوری"}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-white">سایر</span>
<Checkbox
checked={selectedProjectDetails?.developed_technology_type === "سایر"}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
</div>
</div>
</div>
{/* Standards */}
<div className="rounded-lg py-4">
<h3 className="text-sm text-gray-400 mb-4">
استانداردهای ملی و بینالمللی اخذ شده
</h3>
{selectedProjectDetails?.obtained_standard_title && selectedProjectDetails?.obtained_standard_title.length > 0 ? (
<div className="space-y-2">
{(Array.isArray(selectedProjectDetails?.obtained_standard_title)
? selectedProjectDetails?.obtained_standard_title
: [selectedProjectDetails?.obtained_standard_title]
).map((standard, index) => (
<div key={index} className="flex items-center gap-2">
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
<span className="text-sm text-white">{standard}</span>
</div>
))}
</div>
) : (
<p className="text-sm text-gray-500">
هیچ استانداردی ثبت نشده است.
</p>
)}
</div>
{/* Knowledge-based Certificate Button */}
<div className="justify-self-centerr py-1 mx-auto">
{selectedProjectDetails?.knowledge_based_certificate_obtained === "خیر" ? (
<div className=" border border-red-600 rounded-lg p-2 text-center">
<button className="text-red-400 font-medium">
گواهی دانشبنیان ندارد
</button>
</div>
) : (
<Card className="justify-self-center border-emerald-600 bg-transparent py-0">
<CardContent className="p-2 text-center">
<Popover>
<PopoverTrigger asChild>
<Button
variant="default"
className=" text-emerald-400 hover:bg-transparent cursor-pointer bg-transparent"
>
مشاهده اطلاعات گواهی دانشبنیان
</Button>
</PopoverTrigger>
<PopoverContent
className="w-64 bg-gray-900 border border-gray-700 text-right"
align="center"
>
<div className="space-y-2">
<p className="text-sm text-white">
<span className="font-bold">شماره گواهی: </span>
{selectedProjectDetails?.knowledge_based_certificate_number ||
"—"}
</p>
<p className="text-sm text-white">
<span className="font-bold">تاریخ اخذ: </span>
{handleDataValue(selectedProjectDetails?.certificate_obtain_date) || "—"}
</p>
<p className="text-sm text-white">
<span className="font-bold">مرجع صادرکننده: </span>
{selectedProjectDetails?.issuing_authority || "—"}
</p>
</div>
</PopoverContent>
</Popover>
</CardContent>
</Card>
)}
</div>
</div>
{/* Left Column - Project Description and Charts */}
{popupLoading ? (
<div className="lg:col-span-2 border-r-2 flex flex-col gap-2 pr-4 pb-2 border-r-[#5F6284]/50">
<div className="rounded-lg pt-4 flex w-full gap-2">
<Card className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] flex-1 backdrop-blur-sm border-gray-700/50 col-span-2">
<CardContent className="p-2 h-full">
<Skeleton className="h-full w-full" />
</CardContent>
</Card>
<Card className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] flex-1 backdrop-blur-sm border-gray-700/50 col-span-2">
<CardContent className="p-2 h-full">
<Skeleton className="h-full w-full" />
</CardContent>
</Card>
</div>
<div className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] rounded-lg px-6 py-4">
<Skeleton className="h-8 w-1/3 mb-4" />
<Skeleton className="h-60 w-full" />
</div>
<div className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] rounded-lg px-6 py-4">
<Skeleton className="h-8 w-1/3 mb-4" />
<Skeleton className="h-60 w-full" />
</div>
</div>
) : (
<div className="lg:col-span-2 border-r-2 flex flex-col gap-2 pr-4 pb-2 border-r-[#5F6284]/50">
{/* Project Description */}
<div className=" rounded-lg pt-4 flex w-full gap-2">
<Card
className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] flex-1 backdrop-blur-sm border-gray-700/50 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">
میزان صادارت محصول جدید
</h3>
</div>
<div className="flex col-span-1 items-center row-start-2 my-auto col-start-1 justify-center flex-col p-1 my-auto">
<p
className={`text-2xl font-bold mb-1`}
>
{formatNumber(Math.round(popupStats?.new_products_export > 0 ? popupStats?.new_products_export : 0)) || formatNumber(0)}
</p>
<p className="text-xs font-thin text-gray-300 font-persian">
میلیون ریال
</p>
</div>
<span className="text-gray-600 row-start-2 self-center col-start-2 font-thin text-5xl">/</span>
<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-2xl font-bold mb-1`}
>
{formatNumber(Math.round(popupStats?.new_products_export_percent > 0 ? popupStats?.new_products_export_percent : 0)) || formatNumber(0)}%
</p>
<p className="text-xs font-thin text-gray-300 font-persian">
درصد به کل صادرات
</p>
</div>
</div>
</CardContent>
</Card>
<Card
className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] flex-1 backdrop-blur-sm border-gray-700/50 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">
تاثیر در واردات
</h3>
</div>
<div className="flex col-span-1 items-center row-start-2 my-auto col-start-1 justify-center flex-col p-1 my-auto">
<p
className={`text-2xl font-bold mb-1`}
>
{formatNumber(Math.round(popupStats?.import_impact > 0 ? popupStats?.import_impact : 0)) || formatNumber(0)}
</p>
<p className="text-xs font-thin text-gray-300 font-persian">
میلیون ریال
</p>
</div>
<span className="text-gray-600 row-start-2 self-center col-start-2 font-thin text-5xl">/</span>
<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-2xl font-bold mb-1`}
>
{formatNumber(Math.round(popupStats?.import_impact_percent > 0 ? popupStats?.import_impact_percent : 0)) || formatNumber(0)}%
</p>
<p className="text-xs font-thin text-gray-300 font-persian">
درصد صرفه جویی
</p>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Export Revenue Bar Chart */}
<div className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] rounded-lg px-6 py-4">
<h3 className="text-lg font-semibold text-white">ظرفیت صادر شده</h3>
<div className="h-60">
{popupLoading ? (
<div className="flex items-center justify-center h-full">
<div className="animate-pulse my-auto text-gray-400">در حال بارگذاری...</div>
</div>
) : exportChartData.length > 0 ? (
<ResponsiveContainer width="100%" height="100%" >
<BarChart className="aspect-auto w-full"
data={sortedBarData}
barGap={15}
barSize={30}
margin={{
top : 18
}}
>
<CartesianGrid vertical={false} stroke="#475569" />
<XAxis
dataKey="label"
tickLine={false}
axisLine={false}
stroke="#C3C3C3"
tickMargin={8}
tickFormatter={(value: string) => `${value.split(" ")[0]} ${formatNumber(value.split(" ")[1]).replaceAll('٬','')}`}
/>
<YAxis
tickLine={false}
axisLine={false}
stroke="#9CA3AF"
fontSize={11} tick={{ dx: -50 }} tickFormatter={(value: number) => `${formatNumber(value)} میلیون`}/>
<Bar
dataKey="value"
fill="#10B981"
radius={10}
>
<LabelList
formatter={(value : number) => `${formatNumber(value)}`}
position="top"
offset={15}
fill="F9FAFB"
className="fill-foreground"
fontSize={16}
/>
</Bar>
</BarChart>
</ResponsiveContainer>
) : (
<div className="flex items-center justify-center h-full text-gray-400">
دادهای برای نمایش وجود ندارد
</div>
)}
</div>
</div>
{/* Export Revenue Line Chart */}
<div className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] rounded-lg px-6 py-4">
<h3 className="text-lg font-semibold text-white">ظرفیت صادر شده</h3>
<div className="h-60">
{popupLoading ? (
<div className="flex items-center justify-center ">
<div className="animate-pulse my-auto text-gray-400">در حال بارگذاری...</div>
</div>
) : allExportData.length > 0 ? (
<ResponsiveContainer width="100%" height="100%">
<LineChart
accessibilityLayer
className="aspect-auto w-full "
data={transformDataForLineChart(allExportData)}
margin={{ top: 20, right: 30, left: 10, bottom: 50 }}
>
<CartesianGrid vertical={false} stroke="#374151" />
<XAxis
dataKey="season"
stroke="#9CA3AF"
fontSize={11}
tick={({ x, y, payload }) => (
<g transform={`translate(${x},${y + 10})`}>
<text
x={-40}
y={15}
dy={0}
textAnchor="end"
fill="#9CA3AF"
fontSize={11}
transform="rotate(-45)"
>
{payload.value}
</text>
</g>
)}
/>
<YAxis
tickLine={false}
axisLine={false}
stroke="#9CA3AF"
fontSize={11}
tick={{ dx: -50 }}
tickFormatter={(value) => `${formatNumber(value)} میلیون`} // 👈 اضافه کردن M کنار اعداد
/>
<Tooltip
formatter={(value : number) => `${formatNumber(value)} میلیون`}
contentStyle={{
backgroundColor: "#1F2937",
border: "1px solid #374151",
borderRadius: "6px",
padding: "6px 10px",
fontSize: "11px",
color: "#F9FAFB",
}}
/>
<Legend
layout="vertical"
verticalAlign="middle"
align="right"
iconType={"plainline"}display={"flex !important"}
className="!flex"
wrapperStyle={{ fontSize: 11 , paddingLeft : 12 , display : "flex !important" , gap : 10} }
/>
{[...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.product_title}
type="linear"
dot={false}
activeDot={{ r: 5 }}
dataKey={product}
stroke={colors[index % colors.length]}
strokeWidth={2}
/>
);
})}
</LineChart>
</ResponsiveContainer>
) : (
<div className="flex items-center justify-center h-full text-gray-400">
دادهای برای نمایش وجود ندارد
</div>
)}
</div>
</div>
</div>
)}
</div>
</DialogContent>
</Dialog>
</DashboardLayout>
);
}