inogen/app/components/dashboard/dashboard-home.tsx
2025-08-26 13:00:47 +03:30

561 lines
26 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 React, { useState, useEffect } from "react";
import { DashboardLayout } from "./layout";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Progress } from "~/components/ui/progress";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from 'recharts';
import apiService from "~/lib/api";
import toast from "react-hot-toast";
import { Calendar, TrendingUp, TrendingDown, Target, Lightbulb, DollarSign, Minus, CheckCircle, BookOpen } from "lucide-react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
import {
Label,
PolarGrid,
PolarRadiusAxis,
RadialBar,
RadialBarChart,
} from "recharts"
import { ChartContainer } from "~/components/ui/chart"
import { formatNumber } from "~/lib/utils";
export function DashboardHome() {
const [dashboardData, setDashboardData] = useState<any | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchDashboardData();
}, []);
const fetchDashboardData = async () => {
try {
setLoading(true);
setError(null);
// First authenticate if needed
const token = localStorage.getItem('auth_token');
if (!token) {
await apiService.login('inogen_admin', '123456');
}
// Fetch top cards data
const topCardsResponse = await apiService.call({
main_page_first_function: {}
});
// Fetch left section data
const leftCardsResponse = await apiService.call({
main_page_second_function: {}
});
const topCardsResponseData = JSON.parse(topCardsResponse?.data);
const leftCardsResponseData = JSON.parse(leftCardsResponse?.data);
console.log('API Responses:', { topCardsResponseData, leftCardsResponseData });
// Use real API data structure with English keys
const topData = topCardsResponseData || {};
const leftData = leftCardsResponseData || {};
const realData = {
topData: topData,
leftData: leftData,
chartData: leftCardsResponseData?.chartData || []
};
setDashboardData(realData);
} catch (error) {
console.error('Error fetching dashboard data:', error);
const errorMessage = error instanceof Error ? error.message : 'خطای نامشخص';
setError(`خطا در بارگذاری داده‌ها: ${errorMessage}`);
toast.error(`خطا در بارگذاری داده‌ها: ${errorMessage}`);
} finally {
setLoading(false);
}
};
// RadialBarChart data for ideas visualization
const getIdeasChartData = () => {
if (!dashboardData?.topData) return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }];
const registered = parseFloat(dashboardData.topData.registered_innovation_technology_idea || '0');
const ongoing = parseFloat(dashboardData.topData.ongoing_innovation_technology_ideas || '0');
const percentage = registered > 0 ? Math.round((ongoing / registered) * 100) : 0;
return [{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" }];
};
const chartData = getIdeasChartData();
const chartConfig = {
visitors: {
label: "Ideas Progress",
},
safari: {
label: "Safari",
color: "var(--chart-2)",
},
};
if (loading) {
return (
<DashboardLayout>
<div className="p-6">
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
</div>
</DashboardLayout>
);
}
if (error || !dashboardData) {
return (
<DashboardLayout>
<div className="p-6">
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-red-500/50">
<CardContent className="p-6">
<div className="flex flex-col items-center justify-center space-y-4">
<div className="p-4 bg-red-500/20 rounded-full">
<CheckCircle className="w-12 h-12 text-red-400" />
</div>
<h3 className="text-xl font-bold text-red-400 text-center">
خطا در بارگذاری دادهها
</h3>
<p className="text-gray-300 text-center max-w-md">
{error || 'خطای نامشخص در بارگذاری داده‌های داشبورد رخ داده است'}
</p>
<Button
onClick={fetchDashboardData}
variant="outline"
className="border-red-500/50 text-red-400 hover:bg-red-500/10"
>
تلاش مجدد
</Button>
</div>
</CardContent>
</Card>
</div>
</DashboardLayout>
);
}
return (
<DashboardLayout>
<div className="p-3 pb-0 grid grid-cols-3 gap-4">
{/* Top Cards Row - Redesigned to match other components */}
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
{/* Ideas Card */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardContent className="p-4">
<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">
<h3 className="text-lg font-bold text-white font-persian py-2">
ایدههای فناوری و نوآوری
</h3>
</div>
<div className="flex items-center gap-2 justify-center flex-row-reverse p-1">
<ChartContainer config={chartConfig} className="w-full h-full max-h-20 max-w-40">
<RadialBarChart data={chartData} startAngle={0} endAngle={250} innerRadius={30} outerRadius={50}>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-muted last:fill-background"
polarRadius={[36, 24]}
/>
<RadialBar dataKey="visitors" background cornerRadius={5} />
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
<tspan x={viewBox.cx} y={viewBox.cy} className="fill-foreground text-lg font-bold">
{formatNumber(dashboardData.topData?.ongoing_innovation_technology_ideas || '0')}%
</tspan>
</text>
)
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
<div className="font-bold font-persian text-center mt-2">
<div className="flex flex-col justify-between items-center">
<span className="flex font-bold items-center gap-1">
<div className="font-light">ثبت شده :</div>
{formatNumber(dashboardData.topData?.registered_innovation_technology_idea || '0')}
</span>
<span className="flex items-center gap-1 font-bold">
<div className="font-light">در حال اجرا :</div>
{formatNumber(dashboardData.topData?.ongoing_innovation_technology_ideas || '0')}
</span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Revenue Card */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardContent className="p-4">
<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">
<h3 className="text-lg font-bold text-white font-persian">
افزایش درآمد مبتنی بر فناوری و نوآوری
</h3>
</div>
<div className="flex items-center justify-center flex-col p-1">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-2xl font-bold text-green-400">
%{formatNumber(dashboardData.topData?.technology_innovation_based_revenue_growth_percent || '0')}
</p>
<div className="text-xs text-gray-400 font-persian">
درصد به کل درآمد
</div>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-green-400">
{formatNumber(dashboardData.topData?.technology_innovation_based_revenue_growth || '0')}
</p>
<div className="text-xs text-gray-400 font-persian">
میلیون ریال
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Cost Reduction Card */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardContent className="p-4">
<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">
<h3 className="text-lg font-bold text-white font-persian">
کاهش هزینه ها مبتنی بر فناوری و نوآوری
</h3>
</div>
<div className="flex items-center justify-center flex-col p-1">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-2xl font-bold text-orange-400">
%{formatNumber(dashboardData.topData?.technology_innovation_based_cost_reduction_percent || '0')}
</p>
<div className="text-xs text-gray-400 font-persian">
درصد به کل هزینه
</div>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-orange-400">
{formatNumber(Math.round((parseFloat(dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(/,/g, '') || '0')) / 1000000))}
</p>
<div className="text-xs text-gray-400 font-persian">
میلیون ریال
</div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Budget Ratio Card */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardContent className="p-4">
<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">
<h3 className="text-lg font-bold text-white font-persian">
نسبت تحقق بودجه فناوی و نوآوری
</h3>
</div>
<div className="flex items-center gap-2 justify-center flex-row-reverse p-1">
<ChartContainer config={chartConfig} className="w-full h-full max-h-20 max-w-40">
<RadialBarChart data={[{
browser: "budget",
visitors: parseFloat(dashboardData.topData?.innovation_budget_achievement_percent || '0'),
fill: "var(--chart-3)"
}]} startAngle={0} endAngle={250} innerRadius={30} outerRadius={50}>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-muted last:fill-background"
polarRadius={[36, 24]}
/>
<RadialBar dataKey="visitors" background cornerRadius={5} />
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
<tspan x={viewBox.cx} y={viewBox.cy} className="fill-foreground text-lg font-bold">
%{formatNumber(dashboardData.topData?.innovation_budget_achievement_percent || '0')}
</tspan>
</text>
)
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
<div className="font-bold font-persian text-center mt-2">
<div className="flex flex-col justify-between items-center">
<span className="flex font-bold items-center gap-1">
<div className="font-light">مصوب :</div>
{formatNumber(Math.round((parseFloat(dashboardData.topData?.approved_innovation_budget_achievement_ratio?.replace(/,/g, '') || '0')) / 1000000000))}
</span>
<span className="flex items-center gap-1 font-bold">
<div className="font-light">جذب شده :</div>
{formatNumber(Math.round((parseFloat(dashboardData.topData?.allocated_innovation_budget_achievement_ratio?.replace(/,/g, '') || '0')) / 1000000000))}
</span>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Main Content with Tabs */}
<Tabs defaultValue="charts" className=" col-span-2 row-start-2 bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)]">
<TabsList className="bg-transparent">
<TabsTrigger value="charts" className=" text-white data-[state=active]:bg-blue-500/20 data-[state=active]:text-blue-400">
مقایسه ای
</TabsTrigger>
<TabsTrigger value="canvas" disabled className="text-gray-500 cursor-not-allowed">
شماتیک
</TabsTrigger>
</TabsList>
<TabsContent value="charts" className="">
<div className=" gap-6">
{/* Right Section - Charts */}
<div className="">
{/* Main Chart */}
<Card className="bg-transparent px-2 border-none">
<CardHeader>
<CardTitle className="text-white text-xl">تحلیل ارزشها</CardTitle>
<p className="text-gray-400 text-sm">نمودار مقایسهای عملکرد ماهانه</p>
</CardHeader>
<CardContent className="border-none">
<div className="h-60 ">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={[
{ month: 'فروردین', ideas: 12, revenue: 850, cost: 320 },
{ month: 'اردیبهشت', ideas: 19, revenue: 1200, cost: 450 },
{ month: 'خرداد', ideas: 8, revenue: 980, cost: 280 },
{ month: 'تیر', ideas: 15, revenue: 1400, cost: 520 },
{ month: 'مرداد', ideas: 22, revenue: 1650, cost: 680 },
{ month: 'شهریور', ideas: 18, revenue: 1320, cost: 590 }
]}>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis
dataKey="month"
stroke="#9CA3AF"
fontSize={11}
/>
<YAxis stroke="#9CA3AF" fontSize={11} />
<Tooltip
contentStyle={{
backgroundColor: '#1F2937',
border: '1px solid #374151',
borderRadius: '8px',
color: '#F9FAFB'
}}
/>
<Line
type="monotone"
dataKey="ideas"
stroke="#3B82F6"
strokeWidth={3}
name="ایده‌ها"
dot={{ fill: '#3B82F6', strokeWidth: 2, r: 4 }}
/>
<Line
type="monotone"
dataKey="revenue"
stroke="#10B981"
strokeWidth={3}
name="درآمد (میلیون)"
dot={{ fill: '#10B981', strokeWidth: 2, r: 4 }}
/>
<Line
type="monotone"
dataKey="cost"
stroke="#F59E0B"
strokeWidth={3}
name="کاهش هزینه (میلیون)"
dot={{ fill: '#F59E0B', strokeWidth: 2, r: 4 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
</div>
</div>
</TabsContent>
</Tabs>
{/* Left Section - Status Cards */}
<div className="space-y-4 row-start-2 col-spend-1 ">
{/* Technology Intensity */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardContent className="p-4">
<div className="flex items-center gap-3">
<CardTitle className="text-white text-base min-w-[120px]">شدت فناوری</CardTitle>
<Progress value={parseFloat(dashboardData.leftData?.technology_intensity || '0')} className="h-2 flex-1" />
<p className="text-sm text-gray-400 min-w-[60px] text-left">%{formatNumber(dashboardData.leftData?.technology_intensity || '0')}</p>
</div>
</CardContent>
</Card>
{/* Program Status */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardContent className="p-4">
<CustomBarChart
title="وضعیت برنامه‌های فناوری و نوآوری"
loading={loading}
data={[
{
label: "اجرا شده",
value: parseFloat(dashboardData?.leftData?.executed_project || '0'),
color: "bg-green-400",
labelColor: "text-white",
},
{
label: "در حال اجرا",
value: parseFloat(dashboardData?.leftData?.in_progress_project || '0'),
color: "bg-blue-400",
labelColor: "text-white",
},
{
label: "برنامه‌ریزی شده",
value: parseFloat(dashboardData?.leftData?.planned_project || '0'),
color: "bg-red-400",
labelColor: "text-white",
},
]}
barHeight="h-5"
showAxisLabels={false}
/>
</CardContent>
</Card>
{/* Publications */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardHeader className="pb-3">
<CardTitle className="text-white text-lg">انتشارات فناوری و نوآوری</CardTitle>
</CardHeader>
<CardContent className="p-4">
<div className="grid grid-cols-2 grid-rows-2 gap-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-blue-400" />
<span className="text-gray-300 text-sm">کتاب:</span>
</div>
<span className="text-lg font-bold text-blue-400">
{formatNumber(dashboardData.leftData?.printed_books_count || '0')}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-purple-400" />
<span className="text-gray-300 text-sm">پتنت:</span>
</div>
<span className="text-lg font-bold text-purple-400">
{formatNumber(dashboardData.leftData?.registered_patents_count || '0')}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-yellow-400" />
<span className="text-gray-300 text-sm">گزارش:</span>
</div>
<span className="text-lg font-bold text-yellow-400">
{formatNumber(dashboardData.leftData?.published_reports_count || '0')}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-green-400" />
<span className="text-gray-300 text-sm">مقاله:</span>
</div>
<span className="text-lg font-bold text-green-400">
{formatNumber(dashboardData.leftData?.printed_articles_count || '0')}
</span>
</div>
</div>
</CardContent>
</Card>
{/* Promotion */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardHeader className="pb-3">
<CardTitle className="text-white text-lg">ترویج فناوری و نوآوری</CardTitle>
</CardHeader>
<CardContent className="p-4">
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-purple-400" />
<span className="text-gray-300 text-sm">کنفرانس:</span>
</div>
<span className="text-lg font-bold text-purple-400">
{formatNumber(dashboardData.leftData?.attended_conferences_count || '0')}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-blue-400" />
<span className="text-gray-300 text-sm">شرکت در رویداد:</span>
</div>
<span className="text-lg font-bold text-blue-400">
{formatNumber(dashboardData.leftData?.attended_events_count || '0')}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-yellow-400" />
<span className="text-gray-300 text-sm">نمایشگاه:</span>
</div>
<span className="text-lg font-bold text-yellow-400">
{formatNumber(dashboardData.leftData?.attended_exhibitions_count || '0')}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-green-400" />
<span className="text-gray-300 text-sm">برگزاری رویداد:</span>
</div>
<span className="text-lg font-bold text-green-400">
{formatNumber(dashboardData.leftData?.organized_events_count || '0')}
</span>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</DashboardLayout>
);
}
export default DashboardHome;