setup charts in popup also fix bugs in sidebar
This commit is contained in:
parent
4a3b9b6cde
commit
f9fdff1d5a
|
|
@ -22,7 +22,7 @@ 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 { Bar, BarChart, LabelList } from "recharts"
|
||||
import { FunnelChart } from "~/components/ui/funnel-chart";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
|
||||
import {
|
||||
|
|
@ -138,7 +138,6 @@ export function ProductInnovationPage() {
|
|||
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,
|
||||
|
|
@ -200,20 +199,21 @@ export function ProductInnovationPage() {
|
|||
const fetchingRef = useRef(false);
|
||||
|
||||
|
||||
|
||||
const handleProjectDetails = async (project: ProductInnovationData) => {
|
||||
setSelectedProjectDetails(project);
|
||||
setDetailsDialogOpen(true);
|
||||
await fetchPopupData(project.project_id);
|
||||
await fetchPopupData(project);
|
||||
};
|
||||
|
||||
const fetchPopupData = async (projectId: string) => {
|
||||
const fetchPopupData = async (project: ProductInnovationData) => {
|
||||
try {
|
||||
setPopupLoading(true);
|
||||
|
||||
// Fetch popup stats
|
||||
const statsResponse = await apiService.call({
|
||||
innovation_product_popup_function1: {
|
||||
project_id: projectId
|
||||
project_id: project.project_id
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -239,12 +239,12 @@ export function ProductInnovationPage() {
|
|||
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
|
||||
item.product_title === project?.title
|
||||
);
|
||||
setAllExportData(chartData);
|
||||
setExportChartData(filteredData);
|
||||
}
|
||||
}
|
||||
|
|
@ -486,16 +486,15 @@ export function ProductInnovationPage() {
|
|||
|
||||
// Transform data for line chart
|
||||
const transformDataForLineChart = (data: any[]) => {
|
||||
const seasons = [...new Set(data.map(item => item.full_season))].sort();
|
||||
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 ? Math.round(productData.export_revenue_sum / 1000000) : 0;
|
||||
seasonData[product] = productData?.export_revenue_sum > 0 && productData ? Math.round(productData?.export_revenue_sum) : 0;
|
||||
});
|
||||
return seasonData;
|
||||
});
|
||||
|
|
@ -550,7 +549,8 @@ export function ProductInnovationPage() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleProjectDetails(item)}
|
||||
onClick={() => {
|
||||
handleProjectDetails(item)}}
|
||||
className="text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/20 p-2 h-auto"
|
||||
>
|
||||
جزئیات بیشتر
|
||||
|
|
@ -591,6 +591,20 @@ export function ProductInnovationPage() {
|
|||
}
|
||||
};
|
||||
|
||||
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">
|
||||
|
|
@ -619,7 +633,7 @@ export function ProductInnovationPage() {
|
|||
</div>
|
||||
<div className="flex items-center justify-center flex-col p-1">
|
||||
<div
|
||||
className="h-8 bg-gray-600 rounded mb-1 animate-pulse"
|
||||
className="h-8 bg-gray-600 rounded animate-pulse"
|
||||
style={{ width: "40%" }}
|
||||
/>
|
||||
<div
|
||||
|
|
@ -794,7 +808,7 @@ export function ProductInnovationPage() {
|
|||
{columns.map((column) => (
|
||||
<TableCell
|
||||
key={column.key}
|
||||
className="text-center whitespace-nowrap border-emerald-500/20 py-1 px-2"
|
||||
className={`text-right whitespace-nowrap border-emerald-500/20 py-1 px-2`}
|
||||
>
|
||||
{renderCellContent(project, column)}
|
||||
</TableCell>
|
||||
|
|
@ -823,7 +837,7 @@ export function ProductInnovationPage() {
|
|||
<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">
|
||||
<div className="text-base text-gray-401">
|
||||
کل پروژه ها :{formatNumber(stats?.count_innovation_construction_inside_projects)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -836,7 +850,7 @@ export function ProductInnovationPage() {
|
|||
<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="text-base text-gray-400">میانگین :</div>
|
||||
<div className="font-bold">
|
||||
{formatNumber(
|
||||
((stats.average_project_score ?? 0) as number).toFixed?.(1) ?? 0
|
||||
|
|
@ -859,145 +873,6 @@ export function ProductInnovationPage() {
|
|||
</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">
|
||||
|
|
@ -1013,7 +888,7 @@ export function ProductInnovationPage() {
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-emerald-400 mb-1">
|
||||
<div className="text-2xl font-bold text-emerald-400">
|
||||
{formatNumber(popupStats.new_products_export)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-300">میلیون ریال</div>
|
||||
|
|
@ -1035,7 +910,7 @@ export function ProductInnovationPage() {
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-2xl font-bold text-emerald-400 mb-1">
|
||||
<div className="text-2xl font-bold text-emerald-400">
|
||||
{formatNumber(popupStats.import_impact)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-300">میلیون ریال</div>
|
||||
|
|
@ -1106,6 +981,203 @@ export function ProductInnovationPage() {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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 ? (
|
||||
<ResponsiveContainer width="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-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
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -217,6 +217,7 @@ export function Sidebar({
|
|||
if (item.id === "strategic-alignment") {
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
className={cn(
|
||||
"w-full text-right",
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user