inogen/app/components/dashboard/dashboard-home.tsx

856 lines
35 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 { DashboardCustomBarChart } from "./dashboard-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="">
<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="">
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-red-500/50">
<CardContent className="">
<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="py-4 px-0">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
<h3 className="text-lg font-bold text-white font-persian px-6">
ایدههای فناوری و نوآوری
</h3>
</div>
<div className="flex items-center gap-2 justify-center flex-row-reverse">
<ChartContainer
config={chartConfig}
className="w-full h-full max-h-20 max-w-40"
>
<RadialBarChart
data={[
{
browser: "ideas",
visitors:
parseFloat(
dashboardData.topData
?.registered_innovation_technology_idea || "0",
) > 0
? Math.round(
(parseFloat(
dashboardData.topData
?.ongoing_innovation_technology_ideas ||
"0",
) /
parseFloat(
dashboardData.topData
?.registered_innovation_technology_idea ||
"1",
)) *
100,
)
: 0,
fill: "green",
},
]}
startAngle={90}
endAngle={
90 +
((parseFloat(
dashboardData.topData
?.registered_innovation_technology_idea || "0",
) > 0
? Math.round(
(parseFloat(
dashboardData.topData
?.ongoing_innovation_technology_ideas || "0",
) /
parseFloat(
dashboardData.topData
?.registered_innovation_technology_idea ||
"1",
)) *
100,
)
: 0) /
100) *
360
}
innerRadius={35}
outerRadius={55}
>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-red-400 last:fill-background"
polarRadius={[38, 31]}
/>
<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(
parseFloat(
dashboardData.topData
?.registered_innovation_technology_idea ||
"0",
) > 0
? Math.round(
(parseFloat(
dashboardData.topData
?.ongoing_innovation_technology_ideas ||
"0",
) /
parseFloat(
dashboardData.topData
?.registered_innovation_technology_idea ||
"1",
)) *
100,
)
: 0,
)}
</tspan>
</text>
);
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
<div className="font-bold font-persian text-center">
<div className="flex flex-col justify-between items-center gap-2">
<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="py-4 px-0">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
<h3 className="text-lg font-bold text-white font-persian px-6">
افزایش درآمد مبتنی بر فناوری و نوآوری
</h3>
</div>
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-5xl 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>
<span className="text-6xl font-thin text-gray-600">/</span>
<div className="text-center">
<p className="text-5xl font-bold text-green-400">
{formatNumber(
Math.round(
dashboardData.topData
?.technology_innovation_based_revenue_growth_percent,
) || "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="py-4 px-0">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
<h3 className="text-lg font-bold text-white font-persian px-6">
کاهش هزینه ها مبتنی بر فناوری و نوآوری
</h3>
</div>
<div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4">
<div className="text-center">
<p className="text-5xl font-bold text-green-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>
<span className="text-6xl font-thin text-gray-600">/</span>
<div className="text-center">
<p className="text-5xl font-bold text-green-400">
{formatNumber(
Math.round(
dashboardData.topData
?.technology_innovation_based_cost_reduction_percent,
) || "0",
)}
%
</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="py-4 px-0">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
<h3 className="text-lg font-bold text-white font-persian px-6">
نسبت تحقق بودجه فناوی و نوآوری
</h3>
</div>
<div className="flex items-center gap-2 justify-center flex-row-reverse">
<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: "green",
},
]}
startAngle={90}
endAngle={
90 +
(dashboardData.topData
?.innovation_budget_achievement_percent /
100) *
360
}
innerRadius={35}
outerRadius={55}
>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-red-400 last:fill-background"
polarRadius={[38, 31]}
/>
<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(
Math.round(
dashboardData.topData
?.innovation_budget_achievement_percent ||
0,
),
)}
</tspan>
</text>
);
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
<div className="font-bold font-persian text-center">
<div className="flex flex-col justify-between items-center gap-2">
<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-span-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 justify-center gap-1 px-4">
<CardTitle className="text-white text-lg min-w-[120px]">
شدت فناوری
</CardTitle>
<p className="text-base text-left">
%
{formatNumber(
Math.round(
dashboardData.leftData?.technology_intensity || 0,
),
)}
</p>
<Progress
value={parseFloat(
dashboardData.leftData?.technology_intensity || "0",
)}
className="h-4 flex-1"
/>
</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="py-6 px-0">
<DashboardCustomBarChart
title="وضعیت برنامه‌های فناوری و نوآوری"
loading={loading}
data={[
{
label: "اجرا شده",
value: parseFloat(
dashboardData?.leftData?.executed_project || "0",
),
color: "bg-green-400",
},
{
label: "در حال اجرا",
value: parseFloat(
dashboardData?.leftData?.in_progress_project || "0",
),
color: "bg-blue-400",
},
{
label: "برنامه‌ریزی شده",
value: parseFloat(
dashboardData?.leftData?.planned_project || "0",
),
color: "bg-red-400",
},
]}
/>
</CardContent>
</Card>
{/* Publications */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
<CardHeader className="pb-2 border-b-2 border-gray-500/20">
<CardTitle className="text-white text-lg">
انتشارات فناوری و نوآوری
</CardTitle>
</CardHeader>
<CardContent className="p-4">
<div className="grid grid-cols-2 grid-rows-2 gap-4 justify-center">
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-blue-400" />
<span className="text-base">کتاب:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.printed_books_count || "0",
)}
</span>
</div>
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-purple-400" />
<span className="text-base">پتنت:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.registered_patents_count || "0",
)}
</span>
</div>
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-yellow-400" />
<span className="text-base">گزارش:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.published_reports_count || "0",
)}
</span>
</div>
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-green-400" />
<span className="text-base">مقاله:</span>
</div>
<span className="text-xl font-bold ">
{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-2 border-b-2 border-gray-500/20">
<CardTitle className="text-white text-lg">
ترویج فناوری و نوآوری
</CardTitle>
</CardHeader>
<CardContent className="p-4">
<div className="grid grid-cols-2 grid-rows-2 gap-4 justify-center">
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-purple-400" />
<span className="text-base">کنفرانس:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.attended_conferences_count || "0",
)}
</span>
</div>
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-blue-400" />
<span className="text-base">شرکت در رویداد:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.attended_events_count || "0",
)}
</span>
</div>
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-yellow-400" />
<span className="text-base">نمایشگاه:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.attended_exhibitions_count || "0",
)}
</span>
</div>
<div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2">
<BookOpen className="w-4 h-4 text-green-400" />
<span className="text-base">برگزاری رویداد:</span>
</div>
<span className="text-xl font-bold ">
{formatNumber(
dashboardData.leftData?.organized_events_count || "0",
)}
</span>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</DashboardLayout>
);
}
export default DashboardHome;