From fd9a178df7ba3c49b59d3ba7293568942e83a704 Mon Sep 17 00:00:00 2001 From: Saeed Abadiyan Date: Tue, 26 Aug 2025 12:06:52 +0330 Subject: [PATCH] refactor the call api function,also update the componenet in main page (#3) Reviewed-on: https://git.pelekan.org/Saeed0920/inogen/pulls/3 Co-authored-by: Saeed Abadiyan Co-committed-by: Saeed Abadiyan --- app/components/dashboard/dashboard-home.tsx | 571 +++++++++++++++++- app/components/dashboard/header.tsx | 2 +- .../process-innovation-page.tsx | 2 +- app/components/ecosystem/info-panel.tsx | 10 +- app/components/ecosystem/network-graph.tsx | 12 +- app/components/ui/chart.tsx | 351 +++++++++++ app/components/ui/progress.tsx | 26 + app/components/ui/tabs.tsx | 93 +++ app/lib/api.ts | 2 +- package.json | 4 +- pnpm-lock.yaml | 345 +++++++---- 11 files changed, 1249 insertions(+), 169 deletions(-) create mode 100644 app/components/ui/chart.tsx create mode 100644 app/components/ui/progress.tsx create mode 100644 app/components/ui/tabs.tsx diff --git a/app/components/dashboard/dashboard-home.tsx b/app/components/dashboard/dashboard-home.tsx index 19b57a6..affbb0b 100644 --- a/app/components/dashboard/dashboard-home.tsx +++ b/app/components/dashboard/dashboard-home.tsx @@ -1,44 +1,557 @@ -import React from "react"; +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(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( + +
+
+
+
+
+
+ ); + } + + if (error || !dashboardData) { + return ( + +
+ + +
+
+ +
+

+ خطا در بارگذاری داده‌ها +

+

+ {error || 'خطای نامشخص در بارگذاری داده‌های داشبورد رخ داده است'} +

+ +
+
+
+
+
+ ); + } + + + return ( -
- {/* Main Content Area - Empty for now */} -
- - - -
-
-
- - - +
+ {/* Top Cards Row - Redesigned to match other components */} +
+ {/* Ideas Card */} + + +
+
+

+ ایده‌های فناوری و نوآوری +

+
+
+ + + + + + + + +
+
+ +
ثبت شده :‌
+ {formatNumber(dashboardData.topData?.registered_innovation_technology_idea || '0')} +
+ +
در حال اجرا :
+ {formatNumber(dashboardData.topData?.ongoing_innovation_technology_ideas || '0')} +
+
+
+
+
+
+
+ + {/* Revenue Card */} + + +
+
+

+ افزایش درآمد مبتنی بر فناوری و نوآوری +

+
+
+
+
+

+ %{formatNumber(dashboardData.topData?.technology_innovation_based_revenue_growth_percent || '0')} +

+
+ درصد به کل درآمد +
+
+
+

+ {formatNumber(dashboardData.topData?.technology_innovation_based_revenue_growth || '0')} +

+
+ میلیون ریال +
+
+
+
+
+
+
+ + {/* Cost Reduction Card */} + + +
+
+

+ کاهش هزینه ها مبتنی بر فناوری و نوآوری +

+
+
+
+
+

+ %{formatNumber(dashboardData.topData?.technology_innovation_based_cost_reduction_percent || '0')} +

+
+ درصد به کل هزینه +
+
+
+

+ {formatNumber(Math.round((parseFloat(dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(/,/g, '') || '0')) / 1000000))} +

+
+ میلیون ریال +
+
+
+
+
+
+
+ + {/* Budget Ratio Card */} + + +
+
+

+ نسبت تحقق بودجه فناوی و نوآوری +

+
+
+ + + + + + + + +
+
+ +
مصوب :
+ {formatNumber(Math.round((parseFloat(dashboardData.topData?.approved_innovation_budget_achievement_ratio?.replace(/,/g, '') || '0')) / 1000000000))} +
+ +
جذب شده :
+ {formatNumber(Math.round((parseFloat(dashboardData.topData?.allocated_innovation_budget_achievement_ratio?.replace(/,/g, '') || '0')) / 1000000000))} +
+
-

- صفحه در دست ساخت -

-

- محتوای این بخش به زودی اضافه خواهد شد -

+ + {/* Main Content with Tabs */} + + + + مقایسه ای + + + شماتیک + + + + +
+ {/* Right Section - Charts */} +
+ {/* Main Chart */} + + + تحلیل ارزش‌ها +

نمودار مقایسه‌ای عملکرد ماهانه

+
+ +
+ + + + + + + + + + + +
+
+
+ +
+
+
+
+ + {/* Left Section - Status Cards */} +
+ {/* Technology Intensity */} + + +
+ شدت فناوری + +

%{formatNumber(dashboardData.leftData?.technology_intensity || '0')}

+
+
+
+ + {/* Program Status */} + + + + + + + {/* Publications */} + + + انتشارات فناوری و نوآوری + + +
+
+
+ + کتاب: +
+ + {formatNumber(dashboardData.leftData?.printed_books_count || '0')} + +
+
+
+ + پتنت: +
+ + {formatNumber(dashboardData.leftData?.registered_patents_count || '0')} + +
+
+
+ + گزارش: +
+ + {formatNumber(dashboardData.leftData?.published_reports_count || '0')} + +
+
+
+ + مقاله: +
+ + {formatNumber(dashboardData.leftData?.printed_articles_count || '0')} + +
+
+
+
+ + {/* Promotion */} + + + ترویج فناوری و نوآوری + + +
+
+
+ + کنفرانس: +
+ + {formatNumber(dashboardData.leftData?.conferences_count || '0')} + +
+
+
+ + شرکت در رویداد: +
+ + {formatNumber(dashboardData.leftData?.event_participation_count || '0')} + +
+
+
+ + نمایشگاه: +
+ + {formatNumber(dashboardData.leftData?.exhibitions_count || '0')} + +
+
+
+ + برگزاری رویداد: +
+ + {formatNumber(dashboardData.leftData?.event_organization_count || '0')} + +
+
+
+
+
+ +
); diff --git a/app/components/dashboard/header.tsx b/app/components/dashboard/header.tsx index 5d39efb..f160a85 100644 --- a/app/components/dashboard/header.tsx +++ b/app/components/dashboard/header.tsx @@ -26,7 +26,7 @@ interface HeaderProps { export function Header({ onToggleSidebar, className, - title = "داشبورد", + title = "صفحه اول", }: HeaderProps) { const { user } = useAuth(); const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false); diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx index 0190f3b..259bc35 100644 --- a/app/components/dashboard/project-management/process-innovation-page.tsx +++ b/app/components/dashboard/project-management/process-innovation-page.tsx @@ -394,7 +394,7 @@ export function ProcessInnovationPage() { const fetchStats = async () => { try { setStatsLoading(true); - const raw = await apiService.callInnovationProcess({ + const raw = await apiService.call({ innovation_process_function: {}, }); diff --git a/app/components/ecosystem/info-panel.tsx b/app/components/ecosystem/info-panel.tsx index c455b4d..301ab3b 100644 --- a/app/components/ecosystem/info-panel.tsx +++ b/app/components/ecosystem/info-panel.tsx @@ -68,15 +68,17 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { setIsLoading(true); try { const [countsRes, processRes] = await Promise.all([ - apiService.callInnovationProcess({ - ecosystem_counts_function: {}, + apiService.call({ + ecosystem_count_function: {}, }), - apiService.callInnovationProcess({ + apiService.call({ process_creating_actors_function: {}, }), ]); - setCounts(JSON.parse(countsRes.data)); + setCounts( + JSON.parse(JSON.parse(countsRes.data).ecosystem_count_function)[0], + ); // Process the years data and fill missing years const processedData = processYearsData( diff --git a/app/components/ecosystem/network-graph.tsx b/app/components/ecosystem/network-graph.tsx index 4a82b98..5a82537 100644 --- a/app/components/ecosystem/network-graph.tsx +++ b/app/components/ecosystem/network-graph.tsx @@ -88,7 +88,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { (async () => { setIsLoading(true); try { - const res = await apiService.callInnovationProcess({ + const res = await apiService.call({ graph_production_function: {}, }); if (aborted) return; @@ -155,7 +155,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { // Import apiService for the onClick handler const callAPI = useCallback(async (stage_id: number) => { - return await apiService.callInnovationProcess({ + return await apiService.call({ get_values_workflow_function: { stage_id: stage_id, }, @@ -317,7 +317,8 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { .attr("ry", 8) .attr("fill", categoryToColor[d.category] || "#94A3B8") .attr("stroke", "#FFFFFF") - .attr("stroke-width", 3); + .attr("stroke-width", 3) + .style("pointer-events", "none"); // Add center image if available if (d.imageUrl || d.isCenter) { @@ -399,6 +400,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { // Add hover effects nodeGroup .on("mouseenter", function (event, d) { + if (d.isCenter) return; d3.select(this) .select(d.isCenter ? "rect" : "circle") .attr("filter", "url(#glow)") @@ -419,7 +421,6 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { // Add click handlers nodeGroup.on("click", async function (event, d) { - setIsLoading(true); event.stopPropagation(); // Don't handle center node clicks @@ -452,8 +453,6 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { id: d.id, label: d.label, category: d.category, - stageid: d.stageid, - fields: filteredFields, description: descriptionField?.V || undefined, }; @@ -471,7 +470,6 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { onNodeClick(basicDetails); } } - setIsLoading(false); }); // Update positions on simulation tick diff --git a/app/components/ui/chart.tsx b/app/components/ui/chart.tsx new file mode 100644 index 0000000..12c0d60 --- /dev/null +++ b/app/components/ui/chart.tsx @@ -0,0 +1,351 @@ +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "~/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +function ChartContainer({ + id, + className, + children, + config, + ...props +}: React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] +}) { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +} + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +