-
-
-
+ {/* Top Cards Row - Redesigned to match other components */}
+
+ {/* Ideas Card */}
+
+
+
+
+
+ ایدههای فناوری و نوآوری
+
+
+
+
+ 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}
>
-
-
+
+
+ {
+ if (viewBox && "cx" in viewBox && "cy" in viewBox) {
+ return (
+
+
+ %
+ {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,
+ )}
+
+
+ );
+ }
+ }}
+ />
+
+
+
+
+
+
+ ثبت شده :
+ {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 || "0",
+ )}
+
+
+ میلیون ریال
+
+
+
/
+
+
+ {formatNumber(
+ Math.round(
+ dashboardData.topData
+ ?.technology_innovation_based_revenue_growth_percent,
+ ) || "0",
+ )}
+ %
+
+
+ درصد به کل درآمد
+
+
+
+
+
+
+
+
+ {/* Cost Reduction Card */}
+
+
+
+
+
+ کاهش هزینه ها مبتنی بر فناوری و نوآوری
+
+
+
+
+
+
+ {formatNumber(
+ Math.round(
+ parseFloat(
+ dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(
+ /,/g,
+ "",
+ ) || "0",
+ ) / 1000000,
+ ),
+ )}
+
+
+ میلیون ریال
+
+
+
/
+
+
+ {formatNumber(
+ Math.round(
+ dashboardData.topData
+ ?.technology_innovation_based_cost_reduction_percent,
+ ) || "0",
+ )}
+ %
+
+
+ درصد به کل هزینه
+
+
+
+
+
+
+
+
+ {/* Budget Ratio Card */}
+
+
+
+
+
+ نسبت تحقق بودجه فناوی و نوآوری
+
+
+
+
+
+
+
+
+ {
+ if (viewBox && "cx" in viewBox && "cy" in viewBox) {
+ return (
+
+
+ %
+ {formatNumber(
+ Math.round(
+ dashboardData.topData
+ ?.innovation_budget_achievement_percent ||
+ 0,
+ ),
+ )}
+
+
+ );
+ }
+ }}
+ />
+
+
+
+
+
+
+ مصوب :
+ {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 */}
+
+
+
+ تحقق ارزش ها
+
+
+
+ شماتیک
+
+
+ مقایسه ای
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Left Section - Status Cards */}
+
+ {/* Technology Intensity */}
+
+
+
+
+ شدت فناوری
+
+
+ %
+ {formatNumber(
+ Math.round(
+ 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?.attended_conferences_count || "0",
+ )}
+
+
+
+
+
+ شرکت در رویداد:
+
+
+ {formatNumber(
+ dashboardData.leftData?.attended_events_count || "0",
+ )}
+
+
+
+
+
+ نمایشگاه:
+
+
+ {formatNumber(
+ dashboardData.leftData?.attended_exhibitions_count || "0",
+ )}
+
+
+
+
+
+ برگزاری رویداد:
+
+
+ {formatNumber(
+ dashboardData.leftData?.organized_events_count || "0",
+ )}
+
diff --git a/app/components/dashboard/dashboard-layout.tsx b/app/components/dashboard/dashboard-layout.tsx
index a320bda..8406aa6 100644
--- a/app/components/dashboard/dashboard-layout.tsx
+++ b/app/components/dashboard/dashboard-layout.tsx
@@ -144,145 +144,8 @@ export function DashboardHome() {
-
- 24
-
- +2 از ماه گذشته
-
-
-
-
-
-
-
- پروژههای فعال
-
-
-
-
-
-
-
- 12
-
- +1 از هفته گذشته
-
-
-
-
-
-
-
- پروژههای تکمیل شده
-
-
-
-
-
-
- 8
-
- +3 از ماه گذشته
-
-
-
-
-
-
-
- درصد موفقیت
-
-
-
-
-
-
- 85%
-
- +5% از ماه گذشته
-
-
-
- {/* Recent Projects */}
-
-
- پروژههای اخیر
-
-
-
- {[
- {
- name: "سیستم مدیریت محتوا",
- status: "در حال انجام",
- progress: 75,
- },
- { name: "اپلیکیشن موبایل", status: "تکمیل شده", progress: 100 },
- {
- name: "پلتفرم تجارت الکترونیک",
- status: "شروع شده",
- progress: 25,
- },
- {
- name: "سیستم مدیریت مالی",
- status: "در حال بررسی",
- progress: 10,
- },
- ].map((project, index) => (
-
-
-
-
- {project.name}
-
-
- {project.status}
-
-
-
-
-
- {project.progress}%
-
-
-
- ))}
-
-
-
);
diff --git a/app/components/dashboard/header.tsx b/app/components/dashboard/header.tsx
index 5d39efb..32c7fb0 100644
--- a/app/components/dashboard/header.tsx
+++ b/app/components/dashboard/header.tsx
@@ -4,7 +4,7 @@ import { Link } from "react-router";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button";
import {
-PanelLeft,
+ PanelLeft,
Search,
Bell,
Settings,
@@ -26,7 +26,7 @@ interface HeaderProps {
export function Header({
onToggleSidebar,
className,
- title = "داشبورد",
+ title = "صفحه اول",
}: HeaderProps) {
const { user } = useAuth();
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
@@ -54,7 +54,9 @@ export function Header({
)}
{/* Page Title */}
-
{title}
+
+ {title}
+
{/* Right Section */}
diff --git a/app/components/dashboard/interactive-bar-chart.tsx b/app/components/dashboard/interactive-bar-chart.tsx
new file mode 100644
index 0000000..703e448
--- /dev/null
+++ b/app/components/dashboard/interactive-bar-chart.tsx
@@ -0,0 +1,125 @@
+import { Bar, BarChart, CartesianGrid, XAxis, YAxis, LabelList } from "recharts";
+import React, { useState, useEffect } from "react";
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "~/components/ui/card";
+import {
+ type ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from "~/components/ui/chart";
+
+export const description = "An interactive bar chart";
+
+const chartData = [
+ { category: "کیمیا", ideas: 12, revenue: 850, cost: 320 },
+ { category: "ُفرآروزش", ideas: 19, revenue: 1200, cost: 450 },
+ { category: "خوارزمی", ideas: 15, revenue: 1400, cost: 520 },
+];
+
+const chartConfig = {
+ ideas: {
+ label: "ایدهها",
+ color: "#60A5FA", // Blue-400
+ },
+ revenue: {
+ label: "درآمد (میلیون)",
+ color: "#4ADE80", // Green-400
+ },
+ cost: {
+ label: "کاهش هزینه (میلیون)",
+ color: "#F87171", // Red-400
+ },
+} satisfies ChartConfig;
+
+export function InteractiveBarChart() {
+ const [activeChart, setActiveChart] =
+ React.useState
("ideas");
+
+ const total = React.useMemo(
+ () => ({
+ ideas: chartData.reduce((acc, curr) => acc + curr.ideas, 0),
+ revenue: chartData.reduce((acc, curr) => acc + curr.revenue, 0),
+ cost: chartData.reduce((acc, curr) => acc + curr.cost, 0),
+ }),
+ [],
+ );
+
+ return (
+
+
+
+
+
+
+ `${value}%`}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/components/dashboard/project-management/digital-innovation-page.tsx b/app/components/dashboard/project-management/digital-innovation-page.tsx
new file mode 100644
index 0000000..15a18df
--- /dev/null
+++ b/app/components/dashboard/project-management/digital-innovation-page.tsx
@@ -0,0 +1,1097 @@
+import { useState, useEffect, useCallback, useRef, useMemo } from "react";
+import { DashboardLayout } from "../layout";
+import { Card, CardContent } from "~/components/ui/card";
+import { Button } from "~/components/ui/button";
+import { Badge } from "~/components/ui/badge";
+import { Checkbox } from "~/components/ui/checkbox";
+import moment from "moment-jalaali";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "~/components/ui/table";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "~/components/ui/dialog";
+import {
+ ChevronUp,
+ ChevronDown,
+ RefreshCw,
+ Building2,
+ PickaxeIcon,
+ UserIcon,
+ UsersIcon,
+} from "lucide-react";
+import apiService from "~/lib/api";
+import toast from "react-hot-toast";
+import {
+ Database,
+ Zap,
+ TrendingDown,
+ TrendingUp,
+ Key,
+ Sprout,
+ BrainCircuit,
+ LoaderCircle,
+} from "lucide-react";
+import { CustomBarChart } from "~/components/ui/custom-bar-chart";
+
+moment.loadPersian({ usePersianDigits: true });
+
+interface SortConfig {
+ field: string;
+ direction: "asc" | "desc";
+}
+
+interface StatsCard {
+ id: string;
+ title: string;
+ value: string;
+ description?: string;
+ icon: React.ReactNode;
+ color: string;
+}
+
+// Raw API response interface for digital innovation metrics
+interface DigitalInnovationMetrics {
+ count_innovation_digital_projects: string;
+ increased_revenue: string;
+ increased_revenue_percent: string;
+ reduce_costs: string;
+ reduce_costs_percent: string;
+ reduce_energy_consumption: string;
+ reduce_energy_consumption_percent: string;
+ resource_productivity: string;
+ resource_productivity_percent: string;
+}
+
+// Normalized interface for digital innovation stats
+interface DigitalInnovationStats {
+ // totalDigitalProjects: number;
+ increasedRevenue: number;
+ increasedRevenuePercent: number;
+ reduceCosts: number;
+ reduceCostsPercent: number;
+ reduceEnergyConsumption: number;
+ reduceEnergyConsumptionPercent: number;
+ resourceProductivity: number;
+ resourceProductivityPercent: number;
+}
+
+enum DigitalCardLabel {
+ decreasCost = "کاهش هزینهها",
+ increaseRevenue = "افزایش درآمد",
+ performance = "بهرهوری منابع",
+ decreaseEnergy = "کاهش مصرف انرژی",
+}
+
+enum projectStatus {
+ propozal = "پروپوزال",
+ contract = "پیشنویس قرارداد",
+ inprogress = "در حال انجام",
+ stop = "متوقف شده",
+ mafasa = "مرحله مفاصا",
+ finish = "پایان یافته",
+}
+interface ProcessInnovationData {
+ WorkflowID: number;
+ desired_strategy: string;
+ digital_capability: string;
+ digital_competence: string;
+ digital_puberty_elements: string;
+ innovation_cost_reduction: number | string;
+ operational_plan: string;
+ originality_digital_solution: string;
+ project_description: string;
+ project_no: string;
+ project_rating: number | string;
+ project_status: string;
+ reduce_costs_percent: number;
+ title: string;
+}
+
+interface HouseItem {
+ index: number;
+ color?: string;
+ style?: string;
+}
+
+interface ListItem {
+ label: string;
+ development: number;
+ house: HouseItem[];
+}
+
+const columns = [
+ // { key: "select", label: "", sortable: false, width: "50px" },
+ { 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 function DigitalInnovationPage() {
+ const [projects, setProjects] = useState([]);
+ 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 [actualTotalCount, setActualTotalCount] = useState(0);
+ const [statsLoading, setStatsLoading] = useState(false);
+ const [rating, setRating] = useState([]);
+ const [dialogInfo, setDialogInfo] = useState();
+ const [stats, setStats] = useState({
+ increasedRevenue: 0,
+ increasedRevenuePercent: 0,
+ reduceCosts: 0,
+ reduceCostsPercent: 0,
+ reduceEnergyConsumption: 0,
+ reduceEnergyConsumptionPercent: 0,
+ resourceProductivity: 0,
+ resourceProductivityPercent: 0,
+ });
+ const [sortConfig, setSortConfig] = useState({
+ field: "start_date",
+ direction: "asc",
+ });
+ const [selectedProjects, setSelectedProjects] = useState>(
+ new Set()
+ );
+ const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
+ const [avarage, setAvarage] = useState(0);
+ const observerRef = useRef(null);
+ const fetchingRef = useRef(false);
+
+ // Selection handlers
+ const handleSelectAll = () => {
+ if (selectedProjects.size === projects.length) {
+ setSelectedProjects(new Set());
+ } else {
+ setSelectedProjects(new Set(projects.map((p: any) => p.project_no)));
+ }
+ };
+
+ const handleProjectDetails = (project: ProcessInnovationData) => {
+ const model: ListItem = {
+ label: `فرآیند-${project.WorkflowID}`,
+ development: +project.project_rating,
+ house: [],
+ };
+ setRating([model]);
+ setDialogInfo(project);
+ setDetailsDialogOpen(true);
+ };
+
+ const formatNumber = (value: string | number) => {
+ if (!value) return "0";
+ const numericValue = typeof value === "string" ? parseFloat(value) : value;
+ if (isNaN(numericValue)) return "0";
+ return new Intl.NumberFormat("fa-IR").format(numericValue);
+ };
+
+ const statsCards: StatsCard[] = [
+ {
+ id: "production-stops-prevention",
+ title: DigitalCardLabel.decreasCost,
+ value: formatNumber(stats.reduceCosts.toFixed?.(1) ?? stats.reduceCosts),
+ description: "میلیون ریال کاهش یافته",
+ icon: ,
+ color: "text-emerald-400",
+ },
+ {
+ id: "bottleneck-removal",
+ title: DigitalCardLabel.increaseRevenue,
+ value: formatNumber(stats.increasedRevenue),
+ description: "میلیون ریال افزایش یافته",
+ icon: ,
+ color: "text-emerald-400",
+ },
+
+ {
+ id: "currency-reduction",
+ title: DigitalCardLabel.performance,
+ value: formatNumber(
+ stats.resourceProductivity.toFixed?.(0) ?? stats.resourceProductivity
+ ),
+ description: "هزار تن صرفه جوریی شده",
+ icon: ,
+ color: "text-emerald-400",
+ },
+ {
+ id: "frequent-failures-reduction",
+ title: DigitalCardLabel.decreaseEnergy,
+ value: formatNumber(
+ stats.reduceEnergyConsumption.toFixed?.(1) ??
+ stats.reduceEnergyConsumption
+ ),
+ description: "مگاوات کاهش یافته",
+ icon: ,
+ color: "text-emerald-400",
+ },
+ ];
+
+ const fetchTable = 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_no",
+ "title",
+ "project_status",
+ "project_rating",
+ "project_description",
+ "digital_competence",
+ "originality_digital_solution",
+ "digital_puberty_elements",
+ "digital_capability",
+ "operational_plan",
+ "desired_strategy",
+ "innovation_cost_reduction",
+ "reduce_costs_percent",
+ ],
+ Sorts: [[sortConfig.field, sortConfig.direction]],
+ Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]],
+ Pagination: { PageNumber: pageToFetch, PageSize: pageSize },
+ });
+
+ // console.log(JSON.parse(response.data));
+ if (response.state === 0) {
+ const dataString = response.data;
+ if (dataString && typeof dataString === "string") {
+ try {
+ const parsedData: ProcessInnovationData = JSON.parse(dataString);
+ if (Array.isArray(parsedData)) {
+ if (reset) {
+ setProjects(parsedData);
+ calculateAverage(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 loadMore = useCallback(() => {
+ if (!loadingMore && hasMore && !loading) {
+ setCurrentPage((prev) => prev + 1);
+ }
+ }, [loadingMore, hasMore, loading]);
+
+ useEffect(() => {
+ fetchTable(true);
+ fetchTotalCount();
+ fetchStats();
+ }, [sortConfig]);
+
+ useEffect(() => {
+ if (currentPage > 1) {
+ fetchTable(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",
+ }));
+ fetchTotalCount();
+ fetchStats();
+ setCurrentPage(1);
+ setProjects([]);
+ setHasMore(true);
+ };
+
+ const fetchTotalCount = async () => {
+ try {
+ const response = await apiService.select({
+ ProcessName: "project",
+ OutputFields: ["count(project_no)"],
+ Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]],
+ });
+
+ if (response.state === 0) {
+ const dataString = response.data;
+ if (dataString && typeof dataString === "string") {
+ try {
+ const parsedData = JSON.parse(dataString);
+ if (Array.isArray(parsedData) && parsedData[0]) {
+ const count = parsedData[0].project_no_count || 0;
+ setActualTotalCount(count);
+ // Keep stats in sync if backend stats not yet loaded
+ setStats((prev) => ({ ...prev, totalProjects: count }));
+ }
+ } catch (parseError) {
+ console.error("Error parsing count data:", parseError);
+ }
+ }
+ }
+ } catch (error) {
+ console.error("Error fetching total count:", error);
+ }
+ };
+
+ // Fetch aggregated stats from backend call API (innovation_process_function)
+ const fetchStats = async () => {
+ try {
+ setStatsLoading(true);
+ const raw = await apiService.callInnovationProcess({
+ innovation_digital_function: {},
+ });
+
+ let payload: DigitalInnovationMetrics = raw?.data;
+ if (typeof payload === "string") {
+ try {
+ payload = JSON.parse(payload);
+ } catch {}
+ }
+
+ const parseNum = (v: unknown): number => {
+ if (v == null) return 0;
+ if (typeof v === "number") return v;
+ if (typeof v === "string") {
+ const cleaned = v.replace(/,/g, "").trim();
+ const n = parseFloat(cleaned);
+ return isNaN(n) ? 0 : n;
+ }
+ return 0;
+ };
+ const normalized: DigitalInnovationStats = {
+ increasedRevenue: parseNum(payload?.increased_revenue),
+ increasedRevenuePercent: parseNum(payload?.increased_revenue_percent),
+ reduceCosts: parseNum(payload?.reduce_costs),
+ reduceCostsPercent: parseNum(payload?.reduce_costs_percent),
+ reduceEnergyConsumption: parseNum(payload?.reduce_energy_consumption),
+ reduceEnergyConsumptionPercent: parseNum(
+ payload?.reduce_energy_consumption_percent
+ ),
+ resourceProductivity: parseNum(payload?.resource_productivity),
+ resourceProductivityPercent: parseNum(
+ payload?.resource_productivity_percent
+ ),
+ };
+
+ setStats(normalized);
+ } catch (error) {
+ console.error("Error fetching stats:", error);
+ } finally {
+ setStatsLoading(false);
+ }
+ };
+
+ // const handleRefresh = () => {
+ // fetchingRef.current = false;
+ // setCurrentPage(1);
+ // setProjects([]);
+ // setHasMore(true);
+ // fetchTable(true);
+ // fetchTotalCount();
+ // fetchStats();
+ // };
+
+ 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) + " ریال";
+ };
+
+ const renderProgress = useMemo(() => {
+ const total = 10;
+ for (let i = 0; i < rating.length; i++) {
+ const currentElm = rating[i];
+ currentElm.house = [];
+ const greenBoxes = Math.floor((total * currentElm.development) / 100);
+ const partialPercent =
+ (total * currentElm.development) / 100 - greenBoxes;
+ for (let j = 0; j < greenBoxes; j++) {
+ currentElm.house.push({
+ index: j,
+ color: "!bg-emerald-400",
+ });
+ }
+ if (partialPercent != 0 && greenBoxes != 10)
+ currentElm.house.push({
+ index: greenBoxes + 1,
+ style: `linear-gradient(
+ to right,
+ oklch(76.5% 0.177 163.223) 0%,
+ oklch(76.5% 0.177 163.223) ${partialPercent * 100}%,
+ oklch(55.1% 0.027 264.364) ${partialPercent * 100}%,
+ oklch(55.1% 0.027 264.364) 100%
+ )`,
+ });
+ }
+ }, [rating]);
+
+ const ststusColor = (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";
+ }
+ return el;
+ };
+
+ const renderCellContent = (item: any, column: any) => {
+ const value = item[column.key as keyof ProcessInnovationData];
+
+ switch (column.key) {
+ case "select":
+ return (
+ handleSelectProject(item.project_no)}
+ className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
+ />
+ );
+ case "details":
+ return (
+ handleProjectDetails(item)}
+ className="text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/20 p-2 h-auto cursor-pointer"
+ >
+ جزئیات بیشتر
+
+ );
+ case "amount_currency_reduction":
+ return (
+
+ {formatCurrency(String(value))}
+
+ );
+ case "project_no":
+ return (
+
+ {String(value)}
+
+ );
+ case "title":
+ return {String(value)} ;
+ case "project_status":
+ return (
+
+
+ {String(value)}
+
+ );
+ case "project_rating":
+ return (
+
+ {formatNumber(String(value))}
+
+ );
+ case "reduce_prevention_production_stops":
+ case "throat_removal":
+ case "Reduce_rate_failure":
+ return (
+
+ {formatNumber(String(value))}
+
+ );
+ default:
+ return {String(value) || "-"} ;
+ }
+ };
+
+ const calculateAverage = (data: Array) => {
+ let number = 0;
+ data.map(
+ (item: ProcessInnovationData) => (number = number + +item.project_rating)
+ );
+ setAvarage(number / data.length);
+ };
+
+ return (
+
+
+ {/* Stats Cards */}
+
+
+ {/* Stats Grid */}
+
+ {loading || statsLoading
+ ? // Loading skeleton for stats cards - matching new design
+ Array.from({ length: 4 }).map((_, index) => (
+
+
+
+
+
+ ))
+ : statsCards.map((card) => (
+
+
+
+
+
+ {card.title}
+
+
+ {card.icon}
+
+
+
+
+ {card.value}
+
+
+ {card.description}
+
+
+
+
+
+ ))}
+
+
+
+ {/* Process Impacts Chart */}
+
+ {/* */}
+
+ {/* */}
+
+
+
+ {/* Data Table */}
+
+
+
+
+
+
+ {columns.map((column) => (
+
+ {column.key === "select" ? (
+
+ 0
+ }
+ onCheckedChange={handleSelectAll}
+ className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
+ />
+
+ ) : column.sortable ? (
+ handleSort(column.key)}
+ className="flex items-center gap-2"
+ >
+ {column.label}
+ {sortConfig.field === column.key ? (
+ sortConfig.direction === "asc" ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+ ) : (
+ column.label
+ )}
+
+ ))}
+
+
+
+ {loading ? (
+ // Skeleton loading rows (compact)
+ Array.from({ length: 10 }).map((_, index) => (
+
+ {columns.map((column) => (
+
+
+
+ ))}
+
+ ))
+ ) : projects.length === 0 ? (
+
+
+
+ هیچ پروژهای یافت نشد
+
+
+
+ ) : (
+ projects.map((project, index) => (
+
+ {columns.map((column) => (
+
+ {renderCellContent(project, column)}
+
+ ))}
+
+ ))
+ )}
+
+
+
+
+ {/* Infinite scroll trigger */}
+
+ {loadingMore && (
+
+ )}
+
+
+
+ {/* Footer */}
+
+
+
+
+ کل پروژه ها :{formatNumber(actualTotalCount)}
+
+
+ {/* Project number column - empty */}
+
+ {/* Title column - empty */}
+
+ {/* Project status column - empty */}
+
+
+
+
+
+
+ {/* Project rating column - show average */}
+
+
+ میانگین امتیاز :
+
+
+ {formatNumber(((avarage ?? 0) as number).toFixed?.(1) ?? 0)}
+
+
+
+
+
+
+
+ {/* Project Details Dialog */}
+
+
+
+
+ شرح پروژه
+
+
+
+
+
+ {dialogInfo?.title}
+
+
+ {dialogInfo?.project_description}
+
+
+
ویژگی های اصلی پروژه:
+
+
+
+
+
+ شایستگی دیجیتال:
+
+
+
+ {dialogInfo?.digital_capability}
+
+
+
+
+
+
+ اصالت راهکار دیجیتال:
+
+
+
+ {dialogInfo?.digital_competence}
+
+
+
+
+
+
+ المان های بلوغ دیجیتال:
+
+
+
+ {dialogInfo?.digital_puberty_elements}
+
+
+
+
+
+
+
+
+ توسعه قابلیت های دیجیتال:{" "}
+
+
+
+
+
+ {dialogInfo?.digital_capability}
+
+
+
+
+
+
+
+ برنامه های عملیاتی مرتبط:
+
+
+
+
+
+ {dialogInfo?.operational_plan}
+
+
+
+
+
+
+ استراتژی های مورد نظر:
+
+
+
+
+
+ {dialogInfo?.desired_strategy}
+
+
+ {/*
+
+ قابلیت شماره یک
+
*/}
+ {/*
+
+ قابلیت شماره یک
+
*/}
+
+
+
+
+
+
+
+ کاهش هزینه ها
+
+
+
+
+
+ %{" "}
+ {formatNumber(
+ (
+ Math.round(
+ dialogInfo?.reduce_costs_percent! * 100
+ ) / 100
+ ).toFixed(2)
+ )}
+
+
+ درصد به کل هزینه ها
+
+
+
+
+
+ {formatNumber(+dialogInfo?.innovation_cost_reduction!)}
+
+
+ میلیون ریال
+
+
+
+
+
+
+
+ عنوان فرآیند
+ درصد پیشرفت
+
+
+ {rating.map((el, index) => {
+ return (
+
+
{el.label}
+
+ {Array.from({ length: 10 }, (_, i) => {
+ return (
+
+ );
+ })}
+
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+}
+
+export default DigitalInnovationPage;
diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx
index 0190f3b..6d52d71 100644
--- a/app/components/dashboard/project-management/process-innovation-page.tsx
+++ b/app/components/dashboard/project-management/process-innovation-page.tsx
@@ -34,7 +34,6 @@ import {
import apiService from "~/lib/api";
import toast from "react-hot-toast";
import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react";
-import ProjectDetail from "../projects/project-detail";
moment.loadPersian({ usePersianDigits: true });
interface ProcessInnovationData {
@@ -150,7 +149,6 @@ export function ProcessInnovationPage() {
};
const handleProjectDetails = (project: ProcessInnovationData) => {
- console.log(project);
setSelectedProjectDetails(project);
setDetailsDialogOpen(true);
};
@@ -169,9 +167,9 @@ export function ProcessInnovationPage() {
title: "جلوگیری از توقفات تولید",
value: formatNumber(
stats.productionStopsPreventionSum.toFixed?.(1) ??
- stats.productionStopsPreventionSum,
+ stats.productionStopsPreventionSum,
),
- description: "ظرفیت افزایش یافته",
+ description: "تن افزایش یافته",
icon: ,
color: "text-emerald-400",
},
@@ -196,10 +194,10 @@ export function ProcessInnovationPage() {
},
{
id: "frequent-failures-reduction",
- title: "کاهش خرابیهای پرتکرار",
+ title: "کاهش خرابی های پرتکرار",
value: formatNumber(
stats.frequentFailuresReductionSum.toFixed?.(1) ??
- stats.frequentFailuresReductionSum,
+ stats.frequentFailuresReductionSum,
),
description: "مجموع درصد کاهش خرابی",
icon: ,
@@ -394,7 +392,7 @@ export function ProcessInnovationPage() {
const fetchStats = async () => {
try {
setStatsLoading(true);
- const raw = await apiService.callInnovationProcess({
+ const raw = await apiService.call({
innovation_process_function: {},
});
@@ -402,7 +400,7 @@ export function ProcessInnovationPage() {
if (typeof payload === "string") {
try {
payload = JSON.parse(payload);
- } catch {}
+ } catch { }
}
const parseNum = (v: unknown): number => {
@@ -575,73 +573,73 @@ export function ProcessInnovationPage() {
{loading || statsLoading
? // Loading skeleton for stats cards - matching new design
- Array.from({ length: 4 }).map((_, index) => (
-
-
-
-
-
-
-
+ Array.from({ length: 4 }).map((_, index) => (
+
+
+
+
+
+ ))
: statsCards.map((card) => (
-
-
-
-
-
- {card.title}
-
-
- {card.icon}
-
-
-
-
- {card.value}
-
-
- {card.description}
-
+
+
+
+
+
+ {card.title}
+
+
+ {card.icon}
-
-
- ))}
+
+
+ {card.value}
+
+
+ {card.description}
+
+
+
+
+
+ ))}
{/* Process Impacts Chart */}
-
+
{formatNumber(
((stats.averageScore ?? 0) as number).toFixed?.(1) ??
- stats.averageScore ??
- 0,
+ stats.averageScore ??
+ 0,
)}
@@ -883,9 +881,9 @@ export function ProcessInnovationPage() {
{selectedProjectDetails?.start_date
? moment(
- selectedProjectDetails?.start_date,
- "YYYY-MM-DD",
- ).format("YYYY/MM/DD")
+ selectedProjectDetails?.start_date,
+ "YYYY-MM-DD",
+ ).format("YYYY/MM/DD")
: "-"}
@@ -898,9 +896,9 @@ export function ProcessInnovationPage() {
{selectedProjectDetails?.done_date
? moment(
- selectedProjectDetails?.done_date,
- "YYYY-MM-DD",
- ).format("YYYY/MM/DD")
+ selectedProjectDetails?.done_date,
+ "YYYY-MM-DD",
+ ).format("YYYY/MM/DD")
: "-"}
diff --git a/app/components/dashboard/sidebar.tsx b/app/components/dashboard/sidebar.tsx
index 44e887b..717f204 100644
--- a/app/components/dashboard/sidebar.tsx
+++ b/app/components/dashboard/sidebar.tsx
@@ -149,21 +149,21 @@ export function Sidebar({
React.useEffect(() => {
const autoExpandParents = () => {
const newExpandedItems: string[] = [];
-
+
menuItems.forEach((item) => {
if (item.children) {
const hasActiveChild = item.children.some(
- (child) => child.href && location.pathname === child.href
+ (child) => child.href && location.pathname === child.href,
);
if (hasActiveChild) {
newExpandedItems.push(item.id);
}
}
});
-
+
setExpandedItems(newExpandedItems);
};
-
+
autoExpandParents();
}, [location.pathname]);
@@ -171,10 +171,10 @@ export function Sidebar({
setExpandedItems((prev) => {
// If trying to collapse, check if any child is active
if (prev.includes(itemId)) {
- const item = menuItems.find(menuItem => menuItem.id === itemId);
+ const item = menuItems.find((menuItem) => menuItem.id === itemId);
if (item?.children) {
const hasActiveChild = item.children.some(
- (child) => child.href && location.pathname === child.href
+ (child) => child.href && location.pathname === child.href,
);
// Don't collapse if a child is active
if (hasActiveChild) {
@@ -200,10 +200,12 @@ export function Sidebar({
const renderMenuItem = (item: MenuItem, level = 0) => {
const isActive = isActiveRoute(item.href, item.children);
- const isExpanded = expandedItems.includes(item.id) ||
- (item.children && item.children.some(child =>
- child.href && location.pathname === child.href
- ));
+ const isExpanded =
+ expandedItems.includes(item.id) ||
+ (item.children &&
+ item.children.some(
+ (child) => child.href && location.pathname === child.href,
+ ));
const hasChildren = item.children && item.children.length > 0;
const ItemIcon = item.icon;
@@ -228,7 +230,8 @@ export function Sidebar({
? " text-emerald-400 border-r-2 border-emerald-400"
: "text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300",
isCollapsed && level === 0 && "justify-center px-2",
- item.id === "logout" && "hover:bg-red-500/10 hover:text-red-400",
+ item.id === "logout" &&
+ "hover:bg-red-500/10 hover:text-red-400",
)}
>
- child.href && location.pathname === child.href
- ) && "cursor-not-allowed"
- )}
+ item.children &&
+ item.children.some(
+ (child) => child.href && location.pathname === child.href,
+ ) &&
+ "cursor-not-allowed",
+ )}
onClick={handleClick}
>
@@ -313,9 +319,13 @@ export function Sidebar({
"w-4 h-4 transition-transform duration-200",
isExpanded ? "rotate-180" : "rotate-0",
// Show different color when child is active (cannot collapse)
- item.children && item.children.some(child =>
- child.href && location.pathname === child.href
- ) ? "text-emerald-400" : "text-current"
+ item.children &&
+ item.children.some(
+ (child) =>
+ child.href && location.pathname === child.href,
+ )
+ ? "text-emerald-400"
+ : "text-current",
)}
/>
)}
@@ -324,14 +334,12 @@ export function Sidebar({
)}
-
{/* Submenu */}
{hasChildren && isExpanded && !isCollapsed && (
{item.children?.map((child) => renderMenuItem(child, level + 1))}
)}
-
{/* Tooltip for collapsed state */}
{isCollapsed && level === 0 && (
@@ -361,21 +369,26 @@ export function Sidebar({
{!isCollapsed ? (
- سیستم اینوژن
+ داشبورد اینوژن
نسخه ۰.۱
) : (
-
+
)}
diff --git a/app/components/ecosystem/info-panel.tsx b/app/components/ecosystem/info-panel.tsx
index c455b4d..d373924 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(
@@ -164,7 +166,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
},
{ label: "شتابدهنده", value: parseNumber(counts.accelerator_count) },
{ label: "دانشگاه", value: parseNumber(counts.university_count) },
- { label: "صندوق", value: parseNumber(counts.fund_count) },
+ { label: "صندوق های مالی", value: parseNumber(counts.fund_count) },
{ label: "شرکت", value: parseNumber(counts.company_count) },
]
: [];
@@ -404,6 +406,23 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
+ {/* Footer - MOU Count */}
+ {/*
+
+ تعداد تفاهم نامه ها
+ {formatNumber(counts.mou_count)}
+
+ */}
+
+
+
+ تعداد تفاهم نامه ها
+
+ {formatNumber(counts.mou_count)}
+
+
+
+
تعداد بازیگران
@@ -495,13 +514,6 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {