Compare commits
No commits in common. "eadb58da60150b0b22ed07dd07dc7eb36d30ee2d" and "13dc28404c65268f46e288880b495cb6c5cc3662" have entirely different histories.
eadb58da60
...
13dc28404c
|
|
@ -109,7 +109,7 @@ This document describes the exact implementation of the login page based on the
|
||||||
داشبورد مدیریت فناوری و نوآوری
|
داشبورد مدیریت فناوری و نوآوری
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-300 text-sm font-persian leading-relaxed">
|
<p className="text-gray-300 text-sm font-persian leading-relaxed">
|
||||||
لطفاً نام کاربری و کلمه عبور خود را وارد فهرست خواسته شده وارد
|
لطفاً نام کاربری و پسورد خود را وارد فهرست خواسته شده وارد
|
||||||
<br />
|
<br />
|
||||||
فرمایید.
|
فرمایید.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
import * as d3 from "d3";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "~/components/ui/dialog";
|
|
||||||
|
|
||||||
export type D3ImageInfoProps = {
|
|
||||||
imageUrl?: string;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
width?: number; // fallback width if container size not measured yet
|
|
||||||
height?: number; // fallback height
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* D3ImageInfo
|
|
||||||
* - Renders an image and an information box beside it using D3 within an SVG.
|
|
||||||
* - Includes a clickable "show" chip that opens a popup dialog with more details.
|
|
||||||
*/
|
|
||||||
export function D3ImageInfo({
|
|
||||||
imageUrl = "/placeholder.svg",
|
|
||||||
title = "عنوان آیتم",
|
|
||||||
description = "توضیحات تکمیلی در مورد این آیتم در این قسمت نمایش داده میشود.",
|
|
||||||
width = 800,
|
|
||||||
height = 360,
|
|
||||||
}: D3ImageInfoProps) {
|
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const svgRef = useRef<SVGSVGElement | null>(null);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
// Redraw helper
|
|
||||||
const draw = () => {
|
|
||||||
if (!containerRef.current || !svgRef.current) return;
|
|
||||||
|
|
||||||
const container = containerRef.current;
|
|
||||||
const svg = d3.select(svgRef.current);
|
|
||||||
|
|
||||||
const W = Math.max(480, container.clientWidth || width);
|
|
||||||
const H = Math.max(260, height);
|
|
||||||
|
|
||||||
svg.attr("width", W).attr("height", H);
|
|
||||||
|
|
||||||
// Clear previous content
|
|
||||||
svg.selectAll("*").remove();
|
|
||||||
|
|
||||||
// Layout
|
|
||||||
const padding = 16;
|
|
||||||
const imageAreaWidth = Math.min(300, Math.max(220, W * 0.35));
|
|
||||||
const infoAreaX = padding + imageAreaWidth + padding;
|
|
||||||
const infoAreaWidth = W - infoAreaX - padding;
|
|
||||||
|
|
||||||
// Image area (with rounded border)
|
|
||||||
const imgGroup = svg
|
|
||||||
.append("g")
|
|
||||||
.attr("transform", `translate(${padding}, ${padding})`);
|
|
||||||
|
|
||||||
const imgW = imageAreaWidth;
|
|
||||||
const imgH = H - 2 * padding;
|
|
||||||
|
|
||||||
// Frame
|
|
||||||
imgGroup
|
|
||||||
.append("rect")
|
|
||||||
.attr("width", imgW)
|
|
||||||
.attr("height", imgH)
|
|
||||||
.attr("rx", 10)
|
|
||||||
.attr("ry", 10)
|
|
||||||
.attr("fill", "#1F2937") // gray-800
|
|
||||||
.attr("stroke", "#4B5563") // gray-600
|
|
||||||
.attr("stroke-width", 1.5);
|
|
||||||
|
|
||||||
// Image
|
|
||||||
imgGroup
|
|
||||||
.append("image")
|
|
||||||
.attr("href", imageUrl)
|
|
||||||
.attr("x", 4)
|
|
||||||
.attr("y", 4)
|
|
||||||
.attr("width", imgW - 8)
|
|
||||||
.attr("height", imgH - 8)
|
|
||||||
.attr("preserveAspectRatio", "xMidYMid slice")
|
|
||||||
.attr("clip-path", null);
|
|
||||||
|
|
||||||
// Info area
|
|
||||||
const infoGroup = svg
|
|
||||||
.append("g")
|
|
||||||
.attr("transform", `translate(${infoAreaX}, ${padding})`);
|
|
||||||
|
|
||||||
// Info container
|
|
||||||
infoGroup
|
|
||||||
.append("rect")
|
|
||||||
.attr("width", Math.max(220, infoAreaWidth))
|
|
||||||
.attr("height", imgH)
|
|
||||||
.attr("rx", 12)
|
|
||||||
.attr("ry", 12)
|
|
||||||
.attr("fill", "url(#infoGradient)")
|
|
||||||
.attr("stroke", "#6B7280") // gray-500
|
|
||||||
.attr("stroke-width", 1);
|
|
||||||
|
|
||||||
// Background gradient
|
|
||||||
const defs = svg.append("defs");
|
|
||||||
const gradient = defs
|
|
||||||
.append("linearGradient")
|
|
||||||
.attr("id", "infoGradient")
|
|
||||||
.attr("x1", "0%")
|
|
||||||
.attr("y1", "0%")
|
|
||||||
.attr("x2", "0%")
|
|
||||||
.attr("y2", "100%");
|
|
||||||
|
|
||||||
gradient.append("stop").attr("offset", "0%").attr("stop-color", "#111827"); // gray-900
|
|
||||||
gradient
|
|
||||||
.append("stop")
|
|
||||||
.attr("offset", "100%")
|
|
||||||
.attr("stop-color", "#374151"); // gray-700
|
|
||||||
|
|
||||||
// Title
|
|
||||||
infoGroup
|
|
||||||
.append("text")
|
|
||||||
.attr("x", 16)
|
|
||||||
.attr("y", 36)
|
|
||||||
.attr("fill", "#F9FAFB") // gray-50
|
|
||||||
.attr("font-weight", 700)
|
|
||||||
.attr("font-size", 18)
|
|
||||||
.text(title);
|
|
||||||
|
|
||||||
// Description (wrapped)
|
|
||||||
const wrapText = (text: string, maxWidth: number) => {
|
|
||||||
const words = text.split(/\s+/).reverse();
|
|
||||||
const lines: string[] = [];
|
|
||||||
let line: string[] = [];
|
|
||||||
let t = "";
|
|
||||||
while (words.length) {
|
|
||||||
const word = words.pop()!;
|
|
||||||
const test = (t + " " + word).trim();
|
|
||||||
// Approximate measure using character count
|
|
||||||
const tooLong = test.length * 8 > maxWidth; // 8px avg char width
|
|
||||||
if (tooLong && t.length) {
|
|
||||||
lines.push(t);
|
|
||||||
t = word;
|
|
||||||
} else {
|
|
||||||
t = test;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (t) lines.push(t);
|
|
||||||
return lines;
|
|
||||||
};
|
|
||||||
|
|
||||||
const descMaxWidth = Math.max(200, infoAreaWidth - 32);
|
|
||||||
const descLines = wrapText(description, descMaxWidth);
|
|
||||||
|
|
||||||
descLines.forEach((line, i) => {
|
|
||||||
infoGroup
|
|
||||||
.append("text")
|
|
||||||
.attr("x", 16)
|
|
||||||
.attr("y", 70 + i * 22)
|
|
||||||
.attr("fill", "#E5E7EB") // gray-200
|
|
||||||
.attr("font-size", 14)
|
|
||||||
.text(line);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show button-like chip
|
|
||||||
const chipY = Math.min(imgH - 48, 70 + descLines.length * 22 + 16);
|
|
||||||
const chip = infoGroup
|
|
||||||
.append("g")
|
|
||||||
.attr("class", "show-chip")
|
|
||||||
.style("cursor", "pointer");
|
|
||||||
|
|
||||||
const chipW = 120;
|
|
||||||
const chipH = 36;
|
|
||||||
|
|
||||||
chip
|
|
||||||
.append("rect")
|
|
||||||
.attr("x", 16)
|
|
||||||
.attr("y", chipY)
|
|
||||||
.attr("width", chipW)
|
|
||||||
.attr("height", chipH)
|
|
||||||
.attr("rx", 8)
|
|
||||||
.attr("ry", 8)
|
|
||||||
.attr("fill", "#3B82F6") // blue-500
|
|
||||||
.attr("stroke", "#60A5FA")
|
|
||||||
.attr("stroke-width", 1.5)
|
|
||||||
.attr("opacity", 0.95);
|
|
||||||
|
|
||||||
chip
|
|
||||||
.append("text")
|
|
||||||
.attr("x", 16 + chipW / 2)
|
|
||||||
.attr("y", chipY + chipH / 2 + 5)
|
|
||||||
.attr("text-anchor", "middle")
|
|
||||||
.attr("fill", "#FFFFFF")
|
|
||||||
.attr("font-weight", 700)
|
|
||||||
.text("نمایش");
|
|
||||||
|
|
||||||
// Hover & click
|
|
||||||
chip
|
|
||||||
.on("mouseenter", function () {
|
|
||||||
d3.select(this).select("rect").attr("fill", "#2563EB"); // blue-600
|
|
||||||
})
|
|
||||||
.on("mouseleave", function () {
|
|
||||||
d3.select(this).select("rect").attr("fill", "#3B82F6"); // blue-500
|
|
||||||
})
|
|
||||||
.on("click", () => setOpen(true));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ro = new ResizeObserver(() => draw());
|
|
||||||
if (containerRef.current) ro.observe(containerRef.current);
|
|
||||||
draw();
|
|
||||||
return () => ro.disconnect();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [imageUrl, title, description]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full h-full">
|
|
||||||
<div ref={containerRef} className="w-full h-[380px]">
|
|
||||||
<svg ref={svgRef} className="block w-full h-full"></svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
|
||||||
<DialogContent className="sm:max-w-[520px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="font-persian">{title}</DialogTitle>
|
|
||||||
<DialogDescription className="font-persian">
|
|
||||||
{description}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="mt-4">
|
|
||||||
<img
|
|
||||||
src={imageUrl}
|
|
||||||
alt={title}
|
|
||||||
className="w-full h-60 object-cover rounded-md border border-gray-700"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { formatNumber } from "~/lib/utils";
|
|
||||||
|
|
||||||
interface DataItem {
|
|
||||||
label: string;
|
|
||||||
value: number;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DashboardCustomBarChartProps {
|
|
||||||
title: string;
|
|
||||||
data: DataItem[];
|
|
||||||
loading?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DashboardCustomBarChart({
|
|
||||||
title,
|
|
||||||
data,
|
|
||||||
loading = false,
|
|
||||||
}: DashboardCustomBarChartProps) {
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<h3 className="text-lg font-bold text-white font-persian mb-4 text-center border-b-2 border-gray-500/20 pb-3">
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{[1, 2, 3].map((i) => (
|
|
||||||
<div key={i} className="animate-pulse">
|
|
||||||
<div className="h-12 bg-gray-600/30 rounded-lg"></div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the maximum value for scaling
|
|
||||||
const maxValue = Math.max(...data.map((item) => item.value));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full">
|
|
||||||
<h3 className="text-lg font-bold text-white font-persian mb-4 text-center border-b-2 border-gray-500/20">
|
|
||||||
{title}
|
|
||||||
</h3>
|
|
||||||
<div className="px-4">
|
|
||||||
{data.map((item, index) => {
|
|
||||||
const widthPercentage =
|
|
||||||
maxValue > 0 ? (item.value / maxValue) * 100 : 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={index} className="relative">
|
|
||||||
{/* Bar container */}
|
|
||||||
<div className="relative min-h-6 h-10 rounded-lg overflow-hidden">
|
|
||||||
{/* Animated bar */}
|
|
||||||
<div
|
|
||||||
className={`absolute left-0 h-auto gap-2 top-0 ${item.color} rounded-lg transition-all duration-1000 ease-out flex items-center justify-between px-2`}
|
|
||||||
style={{ width: `${widthPercentage}%` }}
|
|
||||||
>
|
|
||||||
<span className="text-white font-bold text-base">
|
|
||||||
{formatNumber(item.value)}
|
|
||||||
</span>
|
|
||||||
<span className="text-white font-persian font-medium text-sm w-max">
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -4,45 +4,23 @@ import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
import { Progress } from "~/components/ui/progress";
|
import { Progress } from "~/components/ui/progress";
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from 'recharts';
|
||||||
BarChart,
|
|
||||||
Bar,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
ResponsiveContainer,
|
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
} from "recharts";
|
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import { Calendar, TrendingUp, TrendingDown, Target, Lightbulb, DollarSign, Minus, CheckCircle, BookOpen } from "lucide-react";
|
||||||
Calendar,
|
|
||||||
TrendingUp,
|
|
||||||
TrendingDown,
|
|
||||||
Target,
|
|
||||||
Lightbulb,
|
|
||||||
DollarSign,
|
|
||||||
Minus,
|
|
||||||
CheckCircle,
|
|
||||||
BookOpen,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
|
||||||
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
||||||
import { DashboardCustomBarChart } from "./dashboard-custom-bar-chart";
|
|
||||||
import { InteractiveBarChart } from "./interactive-bar-chart";
|
|
||||||
import { D3ImageInfo } from "./d3-image-info";
|
|
||||||
import {
|
import {
|
||||||
Label,
|
Label,
|
||||||
PolarGrid,
|
PolarGrid,
|
||||||
PolarRadiusAxis,
|
PolarRadiusAxis,
|
||||||
RadialBar,
|
RadialBar,
|
||||||
RadialBarChart,
|
RadialBarChart,
|
||||||
} from "recharts";
|
} from "recharts"
|
||||||
import { ChartContainer } from "~/components/ui/chart";
|
import { ChartContainer } from "~/components/ui/chart"
|
||||||
import { formatNumber } from "~/lib/utils";
|
import { formatNumber } from "~/lib/utils";
|
||||||
|
|
||||||
|
|
||||||
export function DashboardHome() {
|
export function DashboardHome() {
|
||||||
const [dashboardData, setDashboardData] = useState<any | null>(null);
|
const [dashboardData, setDashboardData] = useState<any | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -58,28 +36,25 @@ export function DashboardHome() {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// First authenticate if needed
|
// First authenticate if needed
|
||||||
const token = localStorage.getItem("auth_token");
|
const token = localStorage.getItem('auth_token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
await apiService.login("inogen_admin", "123456");
|
await apiService.login('inogen_admin', '123456');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch top cards data
|
// Fetch top cards data
|
||||||
const topCardsResponse = await apiService.call({
|
const topCardsResponse = await apiService.call({
|
||||||
main_page_first_function: {},
|
main_page_first_function: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch left section data
|
// Fetch left section data
|
||||||
const leftCardsResponse = await apiService.call({
|
const leftCardsResponse = await apiService.call({
|
||||||
main_page_second_function: {},
|
main_page_second_function: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const topCardsResponseData = JSON.parse(topCardsResponse?.data);
|
const topCardsResponseData = JSON.parse(topCardsResponse?.data);
|
||||||
const leftCardsResponseData = JSON.parse(leftCardsResponse?.data);
|
const leftCardsResponseData = JSON.parse(leftCardsResponse?.data);
|
||||||
|
|
||||||
console.log("API Responses:", {
|
console.log('API Responses:', { topCardsResponseData, leftCardsResponseData });
|
||||||
topCardsResponseData,
|
|
||||||
leftCardsResponseData,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use real API data structure with English keys
|
// Use real API data structure with English keys
|
||||||
const topData = topCardsResponseData || {};
|
const topData = topCardsResponseData || {};
|
||||||
|
|
@ -87,13 +62,12 @@ export function DashboardHome() {
|
||||||
const realData = {
|
const realData = {
|
||||||
topData: topData,
|
topData: topData,
|
||||||
leftData: leftData,
|
leftData: leftData,
|
||||||
chartData: leftCardsResponseData?.chartData || [],
|
chartData: leftCardsResponseData?.chartData || []
|
||||||
};
|
};
|
||||||
setDashboardData(realData);
|
setDashboardData(realData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching dashboard data:", error);
|
console.error('Error fetching dashboard data:', error);
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : 'خطای نامشخص';
|
||||||
error instanceof Error ? error.message : "خطای نامشخص";
|
|
||||||
setError(`خطا در بارگذاری دادهها: ${errorMessage}`);
|
setError(`خطا در بارگذاری دادهها: ${errorMessage}`);
|
||||||
toast.error(`خطا در بارگذاری دادهها: ${errorMessage}`);
|
toast.error(`خطا در بارگذاری دادهها: ${errorMessage}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -103,21 +77,13 @@ export function DashboardHome() {
|
||||||
|
|
||||||
// RadialBarChart data for ideas visualization
|
// RadialBarChart data for ideas visualization
|
||||||
const getIdeasChartData = () => {
|
const getIdeasChartData = () => {
|
||||||
if (!dashboardData?.topData)
|
if (!dashboardData?.topData) return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }];
|
||||||
return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }];
|
|
||||||
|
|
||||||
const registered = parseFloat(
|
const registered = parseFloat(dashboardData.topData.registered_innovation_technology_idea || '0');
|
||||||
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;
|
||||||
const ongoing = parseFloat(
|
|
||||||
dashboardData.topData.ongoing_innovation_technology_ideas || "0",
|
|
||||||
);
|
|
||||||
const percentage =
|
|
||||||
registered > 0 ? Math.round((ongoing / registered) * 100) : 0;
|
|
||||||
|
|
||||||
return [
|
return [{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" }];
|
||||||
{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" },
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartData = getIdeasChartData();
|
const chartData = getIdeasChartData();
|
||||||
|
|
@ -132,81 +98,13 @@ export function DashboardHome() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skeleton component for cards
|
|
||||||
const SkeletonCard = ({ className = "" }) => (
|
|
||||||
<div
|
|
||||||
className={`bg-gray-700/50 rounded-lg overflow-hidden animate-pulse ${className}`}
|
|
||||||
>
|
|
||||||
<div className="p-6">
|
|
||||||
<div className="h-6 bg-gray-600 rounded w-3/4 mb-4"></div>
|
|
||||||
<div className="h-4 bg-gray-600 rounded w-1/2 mb-6"></div>
|
|
||||||
<div className="h-3 bg-gray-600 rounded w-full mb-2"></div>
|
|
||||||
<div className="h-3 bg-gray-600 rounded w-5/6"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Skeleton for the chart
|
|
||||||
const SkeletonChart = () => (
|
|
||||||
<div className="bg-gray-700/50 rounded-lg overflow-hidden animate-pulse p-6">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<div className="h-6 bg-gray-600 rounded w-1/4"></div>
|
|
||||||
<div className="flex space-x-2 rtl:space-x-reverse">
|
|
||||||
<div className="h-8 w-24 bg-gray-600 rounded"></div>
|
|
||||||
<div className="h-8 w-24 bg-gray-600 rounded"></div>
|
|
||||||
<div className="h-8 w-24 bg-gray-600 rounded"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="h-64 bg-gray-800/50 rounded-lg flex items-end space-x-1 rtl:space-x-reverse p-4">
|
|
||||||
{[...Array(12)].map((_, i) => (
|
|
||||||
<div key={i} className="flex-1 flex space-x-1 rtl:space-x-reverse">
|
|
||||||
<div
|
|
||||||
className="w-full bg-blue-400/30 rounded-t-sm"
|
|
||||||
style={{ height: `${Math.random() * 80 + 20}%` }}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="w-full bg-green-400/30 rounded-t-sm"
|
|
||||||
style={{ height: `${Math.random() * 80 + 20}%` }}
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="w-full bg-red-400/30 rounded-t-sm"
|
|
||||||
style={{ height: `${Math.random() * 80 + 20}%` }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between mt-4">
|
|
||||||
{[...Array(6)].map((_, i) => (
|
|
||||||
<div key={i} className="h-3 bg-gray-600 rounded w-1/6"></div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="p-3 pb-0 grid grid-cols-3 gap-4 animate-pulse">
|
<div className="p-6">
|
||||||
{/* Top Cards Row */}
|
<div className="flex items-center justify-center h-64">
|
||||||
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
||||||
<SkeletonCard />
|
|
||||||
<SkeletonCard />
|
|
||||||
<SkeletonCard />
|
|
||||||
<SkeletonCard />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Middle Section */}
|
|
||||||
<div className="col-span-2 space-y-6 h-full">
|
|
||||||
{/* Chart Section */}
|
|
||||||
<SkeletonChart />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Sidebar */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<SkeletonCard />
|
|
||||||
<SkeletonCard />
|
|
||||||
<SkeletonCard />
|
|
||||||
<SkeletonCard />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
|
|
@ -216,9 +114,9 @@ export function DashboardHome() {
|
||||||
if (error || !dashboardData) {
|
if (error || !dashboardData) {
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="">
|
<div className="p-6">
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-red-500/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-red-500/50">
|
||||||
<CardContent className="">
|
<CardContent className="p-6">
|
||||||
<div className="flex flex-col items-center justify-center space-y-4">
|
<div className="flex flex-col items-center justify-center space-y-4">
|
||||||
<div className="p-4 bg-red-500/20 rounded-full">
|
<div className="p-4 bg-red-500/20 rounded-full">
|
||||||
<CheckCircle className="w-12 h-12 text-red-400" />
|
<CheckCircle className="w-12 h-12 text-red-400" />
|
||||||
|
|
@ -227,8 +125,7 @@ export function DashboardHome() {
|
||||||
خطا در بارگذاری دادهها
|
خطا در بارگذاری دادهها
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-300 text-center max-w-md">
|
<p className="text-gray-300 text-center max-w-md">
|
||||||
{error ||
|
{error || 'خطای نامشخص در بارگذاری دادههای داشبورد رخ داده است'}
|
||||||
"خطای نامشخص در بارگذاری دادههای داشبورد رخ داده است"}
|
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={fetchDashboardData}
|
onClick={fetchDashboardData}
|
||||||
|
|
@ -245,6 +142,8 @@ export function DashboardHome() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="p-3 pb-0 grid grid-cols-3 gap-4">
|
<div className="p-3 pb-0 grid grid-cols-3 gap-4">
|
||||||
|
|
@ -252,148 +151,51 @@ export function DashboardHome() {
|
||||||
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
||||||
{/* Ideas Card */}
|
{/* Ideas Card */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||||
<CardContent className="py-4 px-0">
|
<CardContent className="p-4">
|
||||||
<div className="flex flex-col justify-between gap-2">
|
<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">
|
<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 px-6">
|
<h3 className="text-lg font-bold text-white font-persian py-2">
|
||||||
ایدههای فناوری و نوآوری
|
ایدههای فناوری و نوآوری
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 justify-center flex-row-reverse">
|
<div className="flex items-center gap-2 justify-center flex-row-reverse p-1">
|
||||||
<ChartContainer
|
<ChartContainer config={chartConfig} className="w-full h-full max-h-20 max-w-40">
|
||||||
config={chartConfig}
|
<RadialBarChart data={chartData} startAngle={0} endAngle={250} innerRadius={30} outerRadius={50}>
|
||||||
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
|
<PolarGrid
|
||||||
gridType="circle"
|
gridType="circle"
|
||||||
radialLines={false}
|
radialLines={false}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
className="first:fill-red-400 last:fill-background"
|
className="first:fill-muted last:fill-background"
|
||||||
polarRadius={[38, 31]}
|
polarRadius={[36, 24]}
|
||||||
/>
|
/>
|
||||||
<RadialBar
|
<RadialBar dataKey="visitors" background cornerRadius={5} />
|
||||||
dataKey="visitors"
|
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||||
background
|
|
||||||
cornerRadius={5}
|
|
||||||
/>
|
|
||||||
<PolarRadiusAxis
|
|
||||||
tick={false}
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
>
|
|
||||||
<Label
|
<Label
|
||||||
content={({ viewBox }) => {
|
content={({ viewBox }) => {
|
||||||
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||||
return (
|
return (
|
||||||
<text
|
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
|
||||||
x={viewBox.cx}
|
<tspan x={viewBox.cx} y={viewBox.cy} className="fill-foreground text-lg font-bold">
|
||||||
y={viewBox.cy}
|
{formatNumber(dashboardData.topData?.ongoing_innovation_technology_ideas || '0')}%
|
||||||
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>
|
</tspan>
|
||||||
|
|
||||||
</text>
|
</text>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PolarRadiusAxis>
|
</PolarRadiusAxis>
|
||||||
</RadialBarChart>
|
</RadialBarChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
<div className="font-bold font-persian text-center">
|
<div className="font-bold font-persian text-center mt-2">
|
||||||
<div className="flex flex-col justify-between items-center gap-2">
|
<div className="flex flex-col justify-between items-center">
|
||||||
<span className="flex font-bold items-center gap-1">
|
<span className="flex font-bold items-center gap-1">
|
||||||
<div className="font-light">ثبت شده :</div>
|
<div className="font-light">ثبت شده :</div>
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.topData?.registered_innovation_technology_idea || '0')}
|
||||||
dashboardData.topData
|
|
||||||
?.registered_innovation_technology_idea || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1 font-bold">
|
<span className="flex items-center gap-1 font-bold">
|
||||||
<div className="font-light">در حال اجرا :</div>
|
<div className="font-light">در حال اجرا :</div>
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.topData?.ongoing_innovation_technology_ideas || '0')}
|
||||||
dashboardData.topData
|
|
||||||
?.ongoing_innovation_technology_ideas || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -401,43 +203,34 @@ export function DashboardHome() {
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Revenue Card */}
|
{/* Revenue Card */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||||
<CardContent className="py-4 px-0">
|
<CardContent className="p-4">
|
||||||
<div className="flex flex-col justify-between gap-2">
|
<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">
|
<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 px-6">
|
<h3 className="text-lg font-bold text-white font-persian">
|
||||||
افزایش درآمد مبتنی بر فناوری و نوآوری
|
افزایش درآمد مبتنی بر فناوری و نوآوری
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center flex-col">
|
<div className="flex items-center justify-center flex-col p-1">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-5xl font-bold text-green-400">
|
<p className="text-2xl font-bold text-green-400">
|
||||||
{formatNumber(
|
%{formatNumber(dashboardData.topData?.technology_innovation_based_revenue_growth_percent || '0')}
|
||||||
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>
|
</p>
|
||||||
<div className="text-xs text-gray-400 font-persian">
|
<div className="text-xs text-gray-400 font-persian">
|
||||||
درصد به کل درآمد
|
درصد به کل درآمد
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -446,47 +239,31 @@ export function DashboardHome() {
|
||||||
|
|
||||||
{/* Cost Reduction Card */}
|
{/* Cost Reduction Card */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||||
<CardContent className="py-4 px-0">
|
<CardContent className="p-4">
|
||||||
<div className="flex flex-col justify-between gap-2">
|
<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">
|
<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 px-6">
|
<h3 className="text-lg font-bold text-white font-persian">
|
||||||
کاهش هزینه ها مبتنی بر فناوری و نوآوری
|
کاهش هزینه ها مبتنی بر فناوری و نوآوری
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center flex-col">
|
<div className="flex items-center justify-center flex-col p-1">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-5xl font-bold text-green-400">
|
<p className="text-2xl font-bold text-orange-400">
|
||||||
{formatNumber(
|
%{formatNumber(dashboardData.topData?.technology_innovation_based_cost_reduction_percent || '0')}
|
||||||
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>
|
</p>
|
||||||
<div className="text-xs text-gray-400 font-persian">
|
<div className="text-xs text-gray-400 font-persian">
|
||||||
درصد به کل هزینه
|
درصد به کل هزینه
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -495,116 +272,54 @@ export function DashboardHome() {
|
||||||
|
|
||||||
{/* Budget Ratio Card */}
|
{/* Budget Ratio Card */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||||
<CardContent className="py-4 px-0">
|
<CardContent className="p-4">
|
||||||
<div className="flex flex-col justify-between gap-2">
|
<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">
|
<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 px-6">
|
<h3 className="text-lg font-bold text-white font-persian">
|
||||||
نسبت تحقق بودجه فناوی و نوآوری
|
نسبت تحقق بودجه فناوی و نوآوری
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 justify-center flex-row-reverse">
|
<div className="flex items-center gap-2 justify-center flex-row-reverse p-1">
|
||||||
<ChartContainer
|
<ChartContainer config={chartConfig} className="w-full h-full max-h-20 max-w-40">
|
||||||
config={chartConfig}
|
<RadialBarChart data={[{
|
||||||
className="w-full h-full max-h-20 max-w-40"
|
|
||||||
>
|
|
||||||
<RadialBarChart
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
browser: "budget",
|
browser: "budget",
|
||||||
visitors: parseFloat(
|
visitors: parseFloat(dashboardData.topData?.innovation_budget_achievement_percent || '0'),
|
||||||
dashboardData.topData
|
fill: "var(--chart-3)"
|
||||||
?.innovation_budget_achievement_percent || "0",
|
}]} startAngle={0} endAngle={250} innerRadius={30} outerRadius={50}>
|
||||||
),
|
|
||||||
fill: "green",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
startAngle={90}
|
|
||||||
endAngle={
|
|
||||||
90 +
|
|
||||||
(dashboardData.topData
|
|
||||||
?.innovation_budget_achievement_percent /
|
|
||||||
100) *
|
|
||||||
360
|
|
||||||
}
|
|
||||||
innerRadius={35}
|
|
||||||
outerRadius={55}
|
|
||||||
>
|
|
||||||
<PolarGrid
|
<PolarGrid
|
||||||
gridType="circle"
|
gridType="circle"
|
||||||
radialLines={false}
|
radialLines={false}
|
||||||
stroke="none"
|
stroke="none"
|
||||||
className="first:fill-red-400 last:fill-background"
|
className="first:fill-muted last:fill-background"
|
||||||
polarRadius={[38, 31]}
|
polarRadius={[36, 24]}
|
||||||
/>
|
/>
|
||||||
<RadialBar
|
<RadialBar dataKey="visitors" background cornerRadius={5} />
|
||||||
dataKey="visitors"
|
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||||
background
|
|
||||||
cornerRadius={5}
|
|
||||||
/>
|
|
||||||
<PolarRadiusAxis
|
|
||||||
tick={false}
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
>
|
|
||||||
<Label
|
<Label
|
||||||
content={({ viewBox }) => {
|
content={({ viewBox }) => {
|
||||||
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||||
return (
|
return (
|
||||||
<text
|
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle" dominantBaseline="middle">
|
||||||
x={viewBox.cx}
|
<tspan x={viewBox.cx} y={viewBox.cy} className="fill-foreground text-lg font-bold">
|
||||||
y={viewBox.cy}
|
%{formatNumber(dashboardData.topData?.innovation_budget_achievement_percent || '0')}
|
||||||
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>
|
</tspan>
|
||||||
</text>
|
</text>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</PolarRadiusAxis>
|
</PolarRadiusAxis>
|
||||||
</RadialBarChart>
|
</RadialBarChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
<div className="font-bold font-persian text-center">
|
<div className="font-bold font-persian text-center mt-2">
|
||||||
<div className="flex flex-col justify-between items-center gap-2">
|
<div className="flex flex-col justify-between items-center">
|
||||||
<span className="flex font-bold items-center gap-1">
|
<span className="flex font-bold items-center gap-1">
|
||||||
<div className="font-light">مصوب :</div>
|
<div className="font-light">مصوب :</div>
|
||||||
{formatNumber(
|
{formatNumber(Math.round((parseFloat(dashboardData.topData?.approved_innovation_budget_achievement_ratio?.replace(/,/g, '') || '0')) / 1000000000))}
|
||||||
Math.round(
|
|
||||||
parseFloat(
|
|
||||||
dashboardData.topData?.approved_innovation_budget_achievement_ratio?.replace(
|
|
||||||
/,/g,
|
|
||||||
"",
|
|
||||||
) || "0",
|
|
||||||
) / 1000000000,
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1 font-bold">
|
<span className="flex items-center gap-1 font-bold">
|
||||||
<div className="font-light">جذب شده :</div>
|
<div className="font-light">جذب شده :</div>
|
||||||
{formatNumber(
|
{formatNumber(Math.round((parseFloat(dashboardData.topData?.allocated_innovation_budget_achievement_ratio?.replace(/,/g, '') || '0')) / 1000000000))}
|
||||||
Math.round(
|
|
||||||
parseFloat(
|
|
||||||
dashboardData.topData?.allocated_innovation_budget_achievement_ratio?.replace(
|
|
||||||
/,/g,
|
|
||||||
"",
|
|
||||||
) || "0",
|
|
||||||
) / 1000000000,
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -615,150 +330,173 @@ export function DashboardHome() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Content with Tabs */}
|
{/* Main Content with Tabs */}
|
||||||
<Tabs
|
<Tabs defaultValue="charts" className=" col-span-2 row-start-2 bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)]">
|
||||||
defaultValue="charts"
|
<TabsList className="bg-transparent">
|
||||||
className="grid overflow-hidden rounded-lg grid-rows-[max-content] items-center col-span-2 row-start-2 bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)]"
|
<TabsTrigger value="charts" className=" text-white data-[state=active]:bg-blue-500/20 data-[state=active]:text-blue-400">
|
||||||
>
|
|
||||||
<div className="flex items-center border-b border-gray-600 justify-between gap-2">
|
|
||||||
<p className="p-6 font-persian font-semibold text-lg ">
|
|
||||||
تحقق ارزش ها
|
|
||||||
</p>
|
|
||||||
<TabsList className="bg-transparent py-2 border m-6 border-gray-600">
|
|
||||||
<TabsTrigger value="canvas" className="">
|
|
||||||
شماتیک
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="charts" className=" text-white font-light ">
|
|
||||||
مقایسه ای
|
مقایسه ای
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="canvas" disabled className="text-gray-500 cursor-not-allowed">
|
||||||
|
شماتیک
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
|
||||||
|
|
||||||
<TabsContent value="charts" className="w-ful h-full">
|
<TabsContent value="charts" className="">
|
||||||
<InteractiveBarChart />
|
<div className=" gap-6">
|
||||||
</TabsContent>
|
{/* Right Section - Charts */}
|
||||||
|
<div className="">
|
||||||
<TabsContent value="canvas" className="w-ful h-full">
|
{/* Main Chart */}
|
||||||
<div className="p-4">
|
<Card className="bg-transparent px-2 border-none">
|
||||||
<D3ImageInfo
|
<CardHeader>
|
||||||
imageUrl="/main-circle.png"
|
<CardTitle className="text-white text-xl">تحلیل ارزشها</CardTitle>
|
||||||
title="نمای شماتیک"
|
<p className="text-gray-400 text-sm">نمودار مقایسهای عملکرد ماهانه</p>
|
||||||
description=":"
|
</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>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* Left Section - Status Cards */}
|
{/* Left Section - Status Cards */}
|
||||||
<div className="space-y-4 row-start-2 col-span-1">
|
<div className="space-y-4 row-start-2 col-spend-1 ">
|
||||||
{/* Technology Intensity */}
|
{/* Technology Intensity */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-center justify-center gap-1 px-4">
|
<div className="flex items-center gap-3">
|
||||||
<CardTitle className="text-white text-lg min-w-[120px]">
|
<CardTitle className="text-white text-base min-w-[120px]">شدت فناوری</CardTitle>
|
||||||
شدت فناوری
|
<Progress value={parseFloat(dashboardData.leftData?.technology_intensity || '0')} className="h-2 flex-1" />
|
||||||
</CardTitle>
|
<p className="text-sm text-gray-400 min-w-[60px] text-left">%{formatNumber(dashboardData.leftData?.technology_intensity || '0')}</p>
|
||||||
<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>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Program Status */}
|
{/* Program Status */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||||
<CardContent className="py-6 px-0">
|
<CardContent className="p-4">
|
||||||
<DashboardCustomBarChart
|
<CustomBarChart
|
||||||
title="وضعیت برنامههای فناوری و نوآوری"
|
title="وضعیت برنامههای فناوری و نوآوری"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
label: "اجرا شده",
|
label: "اجرا شده",
|
||||||
value: parseFloat(
|
value: parseFloat(dashboardData?.leftData?.executed_project || '0'),
|
||||||
dashboardData?.leftData?.executed_project || "0",
|
|
||||||
),
|
|
||||||
color: "bg-green-400",
|
color: "bg-green-400",
|
||||||
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "در حال اجرا",
|
label: "در حال اجرا",
|
||||||
value: parseFloat(
|
value: parseFloat(dashboardData?.leftData?.in_progress_project || '0'),
|
||||||
dashboardData?.leftData?.in_progress_project || "0",
|
|
||||||
),
|
|
||||||
color: "bg-blue-400",
|
color: "bg-blue-400",
|
||||||
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "برنامهریزی شده",
|
label: "برنامهریزی شده",
|
||||||
value: parseFloat(
|
value: parseFloat(dashboardData?.leftData?.planned_project || '0'),
|
||||||
dashboardData?.leftData?.planned_project || "0",
|
|
||||||
),
|
|
||||||
color: "bg-red-400",
|
color: "bg-red-400",
|
||||||
|
labelColor: "text-white",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
barHeight="h-5"
|
||||||
|
showAxisLabels={false}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Publications */}
|
{/* Publications */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<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">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-white text-lg">
|
<CardTitle className="text-white text-lg">انتشارات فناوری و نوآوری</CardTitle>
|
||||||
انتشارات فناوری و نوآوری
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="grid grid-cols-2 grid-rows-2 gap-4 justify-center">
|
<div className="grid grid-cols-2 grid-rows-2 gap-3">
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-blue-400" />
|
<BookOpen className="w-4 h-4 text-blue-400" />
|
||||||
<span className="text-base">کتاب:</span>
|
<span className="text-gray-300 text-sm">کتاب:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-blue-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.printed_books_count || '0')}
|
||||||
dashboardData.leftData?.printed_books_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-purple-400" />
|
<BookOpen className="w-4 h-4 text-purple-400" />
|
||||||
<span className="text-base">پتنت:</span>
|
<span className="text-gray-300 text-sm">پتنت:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-purple-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.registered_patents_count || '0')}
|
||||||
dashboardData.leftData?.registered_patents_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-yellow-400" />
|
<BookOpen className="w-4 h-4 text-yellow-400" />
|
||||||
<span className="text-base">گزارش:</span>
|
<span className="text-gray-300 text-sm">گزارش:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-yellow-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.published_reports_count || '0')}
|
||||||
dashboardData.leftData?.published_reports_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-green-400" />
|
<BookOpen className="w-4 h-4 text-green-400" />
|
||||||
<span className="text-base">مقاله:</span>
|
<span className="text-gray-300 text-sm">مقاله:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-green-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.printed_articles_count || '0')}
|
||||||
dashboardData.leftData?.printed_articles_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -767,61 +505,53 @@ export function DashboardHome() {
|
||||||
|
|
||||||
{/* Promotion */}
|
{/* Promotion */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
<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">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-white text-lg">
|
<CardTitle className="text-white text-lg">ترویج فناوری و نوآوری</CardTitle>
|
||||||
ترویج فناوری و نوآوری
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="grid grid-cols-2 grid-rows-2 gap-4 justify-center">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-purple-400" />
|
<BookOpen className="w-4 h-4 text-purple-400" />
|
||||||
<span className="text-base">کنفرانس:</span>
|
<span className="text-gray-300 text-sm">کنفرانس:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-purple-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.conferences_count || '0')}
|
||||||
dashboardData.leftData?.attended_conferences_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-blue-400" />
|
<BookOpen className="w-4 h-4 text-blue-400" />
|
||||||
<span className="text-base">شرکت در رویداد:</span>
|
<span className="text-gray-300 text-sm">شرکت در رویداد:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-blue-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.event_participation_count || '0')}
|
||||||
dashboardData.leftData?.attended_events_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-yellow-400" />
|
<BookOpen className="w-4 h-4 text-yellow-400" />
|
||||||
<span className="text-base">نمایشگاه:</span>
|
<span className="text-gray-300 text-sm">نمایشگاه:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-yellow-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.exhibitions_count || '0')}
|
||||||
dashboardData.leftData?.attended_exhibitions_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-4">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BookOpen className="w-4 h-4 text-green-400" />
|
<BookOpen className="w-4 h-4 text-green-400" />
|
||||||
<span className="text-base">برگزاری رویداد:</span>
|
<span className="text-gray-300 text-sm">برگزاری رویداد:</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold ">
|
<span className="text-lg font-bold text-green-400">
|
||||||
{formatNumber(
|
{formatNumber(dashboardData.leftData?.event_organization_count || '0')}
|
||||||
dashboardData.leftData?.organized_events_count || "0",
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -144,8 +144,145 @@ export function DashboardHome() {
|
||||||
<path d="m22 21-3-3" />
|
<path d="m22 21-3-3" />
|
||||||
</svg>
|
</svg>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">24</div>
|
||||||
|
<p className="text-xs text-muted-foreground font-persian">
|
||||||
|
+2 از ماه گذشته
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium font-persian">
|
||||||
|
پروژههای فعال
|
||||||
|
</CardTitle>
|
||||||
|
<svg
|
||||||
|
className="h-4 w-4 text-muted-foreground"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<rect width="20" height="14" x="2" y="5" rx="2" />
|
||||||
|
<path d="M2 10h20" />
|
||||||
|
</svg>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">12</div>
|
||||||
|
<p className="text-xs text-muted-foreground font-persian">
|
||||||
|
+1 از هفته گذشته
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium font-persian">
|
||||||
|
پروژههای تکمیل شده
|
||||||
|
</CardTitle>
|
||||||
|
<svg
|
||||||
|
className="h-4 w-4 text-muted-foreground"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
||||||
|
</svg>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">8</div>
|
||||||
|
<p className="text-xs text-muted-foreground font-persian">
|
||||||
|
+3 از ماه گذشته
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
|
<CardTitle className="text-sm font-medium font-persian">
|
||||||
|
درصد موفقیت
|
||||||
|
</CardTitle>
|
||||||
|
<svg
|
||||||
|
className="h-4 w-4 text-muted-foreground"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path d="M12 2v20m8-10H4" />
|
||||||
|
</svg>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-2xl font-bold">85%</div>
|
||||||
|
<p className="text-xs text-muted-foreground font-persian">
|
||||||
|
+5% از ماه گذشته
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Recent Projects */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="font-persian">پروژههای اخیر</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{[
|
||||||
|
{
|
||||||
|
name: "سیستم مدیریت محتوا",
|
||||||
|
status: "در حال انجام",
|
||||||
|
progress: 75,
|
||||||
|
},
|
||||||
|
{ name: "اپلیکیشن موبایل", status: "تکمیل شده", progress: 100 },
|
||||||
|
{
|
||||||
|
name: "پلتفرم تجارت الکترونیک",
|
||||||
|
status: "شروع شده",
|
||||||
|
progress: 25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "سیستم مدیریت مالی",
|
||||||
|
status: "در حال بررسی",
|
||||||
|
progress: 10,
|
||||||
|
},
|
||||||
|
].map((project, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex items-center space-x-4 space-x-reverse"
|
||||||
|
>
|
||||||
|
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-sm font-medium text-gray-900 dark:text-white font-persian">
|
||||||
|
{project.name}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 font-persian">
|
||||||
|
{project.status}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2 space-x-reverse">
|
||||||
|
<div className="w-16 bg-gray-200 rounded-full h-2 dark:bg-gray-700">
|
||||||
|
<div
|
||||||
|
className="bg-green-600 h-2 rounded-full"
|
||||||
|
style={{ width: `${project.progress}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{project.progress}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,7 @@ export function Header({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Page Title */}
|
{/* Page Title */}
|
||||||
<h1 className="text-xl flex items-center justify-center gap-4 font-bold text-white font-persian">
|
<h1 className="text-xl flex items-center justify-center gap-4 font-bold text-white font-persian"><PanelLeft /> {title}</h1>
|
||||||
<PanelLeft /> {title}
|
|
||||||
</h1>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Section */}
|
{/* Right Section */}
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
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<keyof typeof chartConfig>("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 (
|
|
||||||
<Card className="py-0 bg-transparent mt-20 border-none h-full">
|
|
||||||
<CardContent className="px-2 sm:p-6 bg-transparent">
|
|
||||||
<ChartContainer
|
|
||||||
config={chartConfig}
|
|
||||||
className="aspect-auto h-96 w-full"
|
|
||||||
>
|
|
||||||
<BarChart
|
|
||||||
accessibilityLayer
|
|
||||||
data={chartData}
|
|
||||||
margin={{
|
|
||||||
left: 12,
|
|
||||||
right: 12,
|
|
||||||
}}
|
|
||||||
barCategoryGap="42%"
|
|
||||||
>
|
|
||||||
<CartesianGrid vertical={false} stroke="#475569" />
|
|
||||||
<XAxis
|
|
||||||
dataKey="category"
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
tickMargin={8}
|
|
||||||
minTickGap={32}
|
|
||||||
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
domain={[0, 100]}
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
tickMargin={8}
|
|
||||||
tick={{ fill: "#94a3b8", fontSize: 12 }}
|
|
||||||
tickFormatter={(value) => `${value}%`}
|
|
||||||
/>
|
|
||||||
<Bar
|
|
||||||
dataKey="ideas"
|
|
||||||
fill={chartConfig.ideas.color}
|
|
||||||
radius={[8, 8, 0, 0]}
|
|
||||||
>
|
|
||||||
<LabelList
|
|
||||||
dataKey="ideas"
|
|
||||||
position="top"
|
|
||||||
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
|
|
||||||
/>
|
|
||||||
</Bar>
|
|
||||||
<Bar
|
|
||||||
dataKey="revenue"
|
|
||||||
fill={chartConfig.revenue.color}
|
|
||||||
radius={[8, 8, 0, 0]}
|
|
||||||
>
|
|
||||||
<LabelList
|
|
||||||
dataKey="revenue"
|
|
||||||
position="top"
|
|
||||||
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
|
|
||||||
/>
|
|
||||||
</Bar>
|
|
||||||
<Bar
|
|
||||||
dataKey="cost"
|
|
||||||
fill={chartConfig.cost.color}
|
|
||||||
radius={[8, 8, 0, 0]}
|
|
||||||
>
|
|
||||||
<LabelList
|
|
||||||
dataKey="cost"
|
|
||||||
position="top"
|
|
||||||
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
|
|
||||||
/>
|
|
||||||
</Bar>
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -34,6 +34,7 @@ import {
|
||||||
import apiService from "~/lib/api";
|
import apiService from "~/lib/api";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react";
|
import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react";
|
||||||
|
import ProjectDetail from "../projects/project-detail";
|
||||||
|
|
||||||
moment.loadPersian({ usePersianDigits: true });
|
moment.loadPersian({ usePersianDigits: true });
|
||||||
interface ProcessInnovationData {
|
interface ProcessInnovationData {
|
||||||
|
|
@ -149,6 +150,7 @@ export function ProcessInnovationPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProjectDetails = (project: ProcessInnovationData) => {
|
const handleProjectDetails = (project: ProcessInnovationData) => {
|
||||||
|
console.log(project);
|
||||||
setSelectedProjectDetails(project);
|
setSelectedProjectDetails(project);
|
||||||
setDetailsDialogOpen(true);
|
setDetailsDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -169,7 +171,7 @@ export function ProcessInnovationPage() {
|
||||||
stats.productionStopsPreventionSum.toFixed?.(1) ??
|
stats.productionStopsPreventionSum.toFixed?.(1) ??
|
||||||
stats.productionStopsPreventionSum,
|
stats.productionStopsPreventionSum,
|
||||||
),
|
),
|
||||||
description: "تن افزایش یافته",
|
description: "ظرفیت افزایش یافته",
|
||||||
icon: <CirclePause />,
|
icon: <CirclePause />,
|
||||||
color: "text-emerald-400",
|
color: "text-emerald-400",
|
||||||
},
|
},
|
||||||
|
|
@ -639,7 +641,7 @@ export function ProcessInnovationPage() {
|
||||||
|
|
||||||
{/* Process Impacts Chart */}
|
{/* Process Impacts Chart */}
|
||||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden">
|
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden">
|
||||||
<CardContent >
|
<CardContent className="p-4">
|
||||||
<CustomBarChart
|
<CustomBarChart
|
||||||
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
|
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
|
||||||
loading={statsLoading}
|
loading={statsLoading}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ export function Sidebar({
|
||||||
menuItems.forEach((item) => {
|
menuItems.forEach((item) => {
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
const hasActiveChild = item.children.some(
|
const hasActiveChild = item.children.some(
|
||||||
(child) => child.href && location.pathname === child.href,
|
(child) => child.href && location.pathname === child.href
|
||||||
);
|
);
|
||||||
if (hasActiveChild) {
|
if (hasActiveChild) {
|
||||||
newExpandedItems.push(item.id);
|
newExpandedItems.push(item.id);
|
||||||
|
|
@ -171,10 +171,10 @@ export function Sidebar({
|
||||||
setExpandedItems((prev) => {
|
setExpandedItems((prev) => {
|
||||||
// If trying to collapse, check if any child is active
|
// If trying to collapse, check if any child is active
|
||||||
if (prev.includes(itemId)) {
|
if (prev.includes(itemId)) {
|
||||||
const item = menuItems.find((menuItem) => menuItem.id === itemId);
|
const item = menuItems.find(menuItem => menuItem.id === itemId);
|
||||||
if (item?.children) {
|
if (item?.children) {
|
||||||
const hasActiveChild = item.children.some(
|
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
|
// Don't collapse if a child is active
|
||||||
if (hasActiveChild) {
|
if (hasActiveChild) {
|
||||||
|
|
@ -200,11 +200,9 @@ export function Sidebar({
|
||||||
|
|
||||||
const renderMenuItem = (item: MenuItem, level = 0) => {
|
const renderMenuItem = (item: MenuItem, level = 0) => {
|
||||||
const isActive = isActiveRoute(item.href, item.children);
|
const isActive = isActiveRoute(item.href, item.children);
|
||||||
const isExpanded =
|
const isExpanded = expandedItems.includes(item.id) ||
|
||||||
expandedItems.includes(item.id) ||
|
(item.children && item.children.some(child =>
|
||||||
(item.children &&
|
child.href && location.pathname === child.href
|
||||||
item.children.some(
|
|
||||||
(child) => child.href && location.pathname === child.href,
|
|
||||||
));
|
));
|
||||||
const hasChildren = item.children && item.children.length > 0;
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
|
|
||||||
|
|
@ -230,8 +228,7 @@ export function Sidebar({
|
||||||
? " text-emerald-400 border-r-2 border-emerald-400"
|
? " 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",
|
: "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",
|
isCollapsed && level === 0 && "justify-center px-2",
|
||||||
item.id === "logout" &&
|
item.id === "logout" && "hover:bg-red-500/10 hover:text-red-400",
|
||||||
"hover:bg-red-500/10 hover:text-red-400",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||||
|
|
@ -272,11 +269,9 @@ export function Sidebar({
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full text-right",
|
"w-full text-right",
|
||||||
// Disable pointer cursor when child is active (cannot collapse)
|
// Disable pointer cursor when child is active (cannot collapse)
|
||||||
item.children &&
|
item.children && item.children.some(child =>
|
||||||
item.children.some(
|
child.href && location.pathname === child.href
|
||||||
(child) => child.href && location.pathname === child.href,
|
) && "cursor-not-allowed"
|
||||||
) &&
|
|
||||||
"cursor-not-allowed",
|
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
|
|
@ -288,8 +283,7 @@ export function Sidebar({
|
||||||
? " text-emerald-400 border-r-2 border-emerald-400"
|
? " 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",
|
: "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",
|
isCollapsed && level === 0 && "justify-center px-2",
|
||||||
item.id === "logout" &&
|
item.id === "logout" && "hover:bg-red-500/10 hover:text-red-400",
|
||||||
"hover:bg-red-500/10 hover:text-red-400",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||||
|
|
@ -319,13 +313,9 @@ export function Sidebar({
|
||||||
"w-4 h-4 transition-transform duration-200",
|
"w-4 h-4 transition-transform duration-200",
|
||||||
isExpanded ? "rotate-180" : "rotate-0",
|
isExpanded ? "rotate-180" : "rotate-0",
|
||||||
// Show different color when child is active (cannot collapse)
|
// Show different color when child is active (cannot collapse)
|
||||||
item.children &&
|
item.children && item.children.some(child =>
|
||||||
item.children.some(
|
child.href && location.pathname === child.href
|
||||||
(child) =>
|
) ? "text-emerald-400" : "text-current"
|
||||||
child.href && location.pathname === child.href,
|
|
||||||
)
|
|
||||||
? "text-emerald-400"
|
|
||||||
: "text-current",
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -334,12 +324,14 @@ export function Sidebar({
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Submenu */}
|
{/* Submenu */}
|
||||||
{hasChildren && isExpanded && !isCollapsed && (
|
{hasChildren && isExpanded && !isCollapsed && (
|
||||||
<div className="mt-1 space-y-0.5">
|
<div className="mt-1 space-y-0.5">
|
||||||
{item.children?.map((child) => renderMenuItem(child, level + 1))}
|
{item.children?.map((child) => renderMenuItem(child, level + 1))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Tooltip for collapsed state */}
|
{/* Tooltip for collapsed state */}
|
||||||
{isCollapsed && level === 0 && (
|
{isCollapsed && level === 0 && (
|
||||||
<div className="absolute right-full top-1/2 transform -translate-y-1/2 mr-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-50">
|
<div className="absolute right-full top-1/2 transform -translate-y-1/2 mr-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-50">
|
||||||
|
|
@ -376,19 +368,14 @@ export function Sidebar({
|
||||||
/>
|
/>
|
||||||
<div className="font-persian">
|
<div className="font-persian">
|
||||||
<div className="text-sm font-semibold text-white">
|
<div className="text-sm font-semibold text-white">
|
||||||
داشبورد اینوژن
|
سیستم اینوژن
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">نسخه ۰.۱</div>
|
<div className="text-xs text-gray-400">نسخه ۰.۱</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex justify-center w-full">
|
<div className="flex justify-center w-full">
|
||||||
<GalleryVerticalEnd
|
<InogenLogo size="sm" />
|
||||||
color="black"
|
|
||||||
size={32}
|
|
||||||
strokeWidth={1}
|
|
||||||
className="bg-green-400 p-1.5 rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
||||||
},
|
},
|
||||||
{ label: "شتابدهنده", value: parseNumber(counts.accelerator_count) },
|
{ label: "شتابدهنده", value: parseNumber(counts.accelerator_count) },
|
||||||
{ label: "دانشگاه", value: parseNumber(counts.university_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) },
|
{ label: "شرکت", value: parseNumber(counts.company_count) },
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -406,23 +406,6 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
{/* Footer - MOU Count */}
|
|
||||||
{/* <CardContent className="py-3">
|
|
||||||
<div className="flex font-bold text-xl px-6 justify-between text-gray-300 font-persian mb-1">
|
|
||||||
تعداد تفاهم نامه ها
|
|
||||||
<span className="text-2xl">{formatNumber(counts.mou_count)}</span>
|
|
||||||
</div>
|
|
||||||
</CardContent> */}
|
|
||||||
|
|
||||||
<CardHeader className="text-center pb-2 border-b-2 border-[#3F415A]">
|
|
||||||
<CardTitle className="font-persian text-xl text-white flex justify-between px-4">
|
|
||||||
تعداد تفاهم نامه ها
|
|
||||||
<span className="font-bold text-3xl">
|
|
||||||
{formatNumber(counts.mou_count)}
|
|
||||||
</span>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardHeader className="text-center pb-2 border-b-2 border-[#3F415A]">
|
<CardHeader className="text-center pb-2 border-b-2 border-[#3F415A]">
|
||||||
<CardTitle className="font-persian text-xl text-white flex justify-between px-4">
|
<CardTitle className="font-persian text-xl text-white flex justify-between px-4">
|
||||||
تعداد بازیگران
|
تعداد بازیگران
|
||||||
|
|
@ -514,6 +497,13 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
|
{/* Footer - MOU Count */}
|
||||||
|
<CardContent className="py-3">
|
||||||
|
<div className="flex font-bold text-xl px-6 justify-between text-gray-300 font-persian mb-1">
|
||||||
|
تعداد تفاهم نامه ها
|
||||||
|
<span className="text-2xl">{formatNumber(counts.mou_count)}</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
||||||
// Create center node
|
// Create center node
|
||||||
const centerNode: Node = {
|
const centerNode: Node = {
|
||||||
id: "center",
|
id: "center",
|
||||||
label: "", //مرکز زیست بوم
|
label: "مرکز اکوسیستم",
|
||||||
category: "center",
|
category: "center",
|
||||||
stageid: 0,
|
stageid: 0,
|
||||||
isCenter: true,
|
isCenter: true,
|
||||||
|
|
@ -453,8 +453,6 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
||||||
id: d.id,
|
id: d.id,
|
||||||
label: d.label,
|
label: d.label,
|
||||||
category: d.category,
|
category: d.category,
|
||||||
stageid: d.stageid,
|
|
||||||
fields: filteredFields,
|
|
||||||
description: descriptionField?.V || undefined,
|
description: descriptionField?.V || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export function CustomBarChart({
|
||||||
// Loading skeleton
|
// Loading skeleton
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className={`space-y-6 p-4 ${className}`} style={{ height }}>
|
<div className={`space-y-6 ${className}`} style={{ height }}>
|
||||||
{title && (
|
{title && (
|
||||||
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
|
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -68,15 +68,13 @@ export function CustomBarChart({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`space-y-6 ${className}`} style={{ height }}>
|
<div className={`space-y-6 ${className}`} style={{ height }}>
|
||||||
<div className="border-b">
|
|
||||||
{title && (
|
{title && (
|
||||||
<h3 className="text-xl font-bold text-white font-persian text-right p-4">
|
<h3 className="text-xl font-bold text-white font-persian text-right mb-4">
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4 px-4 pb-4">
|
<div className="space-y-4">
|
||||||
{data.map((item, index) => {
|
{data.map((item, index) => {
|
||||||
const percentage =
|
const percentage =
|
||||||
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
||||||
|
|
@ -86,7 +84,8 @@ export function CustomBarChart({
|
||||||
<div key={index} className="flex items-center gap-3">
|
<div key={index} className="flex items-center gap-3">
|
||||||
{/* Label */}
|
{/* Label */}
|
||||||
<span
|
<span
|
||||||
className={`font-persian text-sm min-w-[160px] text-right ${item.labelColor || "text-white"
|
className={`font-persian text-sm min-w-[160px] text-right ${
|
||||||
|
item.labelColor || "text-white"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
|
|
@ -97,7 +96,8 @@ export function CustomBarChart({
|
||||||
className={`flex-1 flex items-center bg-gray-700 rounded-full relative overflow-hidden ${barHeight}`}
|
className={`flex-1 flex items-center bg-gray-700 rounded-full relative overflow-hidden ${barHeight}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`${barHeight} rounded-full transition-all duration-700 ease-out relative ${item.color || "bg-emerald-400"
|
className={`${barHeight} rounded-full transition-all duration-700 ease-out relative ${
|
||||||
|
item.color || "bg-emerald-400"
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
width: `${Math.min(percentage, 100)}%`,
|
width: `${Math.min(percentage, 100)}%`,
|
||||||
|
|
@ -110,7 +110,8 @@ export function CustomBarChart({
|
||||||
|
|
||||||
{/* Value Label */}
|
{/* Value Label */}
|
||||||
<span
|
<span
|
||||||
className={`font-bold text-sm min-w-[60px] text-left ${item.color?.includes("emerald")
|
className={`font-bold text-sm min-w-[60px] text-left ${
|
||||||
|
item.color?.includes("emerald")
|
||||||
? "text-emerald-400"
|
? "text-emerald-400"
|
||||||
: item.color?.includes("blue")
|
: item.color?.includes("blue")
|
||||||
? "text-blue-400"
|
? "text-blue-400"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ const Table = React.forwardRef<HTMLTableElement, TableProps>(
|
||||||
<div className={cn("relative w-full", containerClassName)}>
|
<div className={cn("relative w-full", containerClassName)}>
|
||||||
<table
|
<table
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("w-full caption-bottom text-sm h-full", className)}
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,23 +16,17 @@ interface TabsProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tabs({
|
export function Tabs({ defaultValue, value, onValueChange, className, children }: TabsProps) {
|
||||||
defaultValue,
|
|
||||||
value,
|
|
||||||
onValueChange,
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
}: TabsProps) {
|
|
||||||
const [internalValue, setInternalValue] = useState(defaultValue || "");
|
const [internalValue, setInternalValue] = useState(defaultValue || "");
|
||||||
|
|
||||||
const currentValue = value ?? internalValue;
|
const currentValue = value ?? internalValue;
|
||||||
const handleValueChange = onValueChange ?? setInternalValue;
|
const handleValueChange = onValueChange ?? setInternalValue;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabsContext.Provider
|
<TabsContext.Provider value={{ value: currentValue, onValueChange: handleValueChange }}>
|
||||||
value={{ value: currentValue, onValueChange: handleValueChange }}
|
<div className={cn("w-full", className)}>
|
||||||
>
|
{children}
|
||||||
<div className={cn("w-full", className)}>{children}</div>
|
</div>
|
||||||
</TabsContext.Provider>
|
</TabsContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -44,12 +38,7 @@ interface TabsListProps {
|
||||||
|
|
||||||
export function TabsList({ className, children }: TabsListProps) {
|
export function TabsList({ className, children }: TabsListProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cn("inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground", className)}>
|
||||||
className={cn(
|
|
||||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -62,12 +51,7 @@ interface TabsTriggerProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TabsTrigger({
|
export function TabsTrigger({ value, className, disabled, children }: TabsTriggerProps) {
|
||||||
value,
|
|
||||||
className,
|
|
||||||
disabled,
|
|
||||||
children,
|
|
||||||
}: TabsTriggerProps) {
|
|
||||||
const context = useContext(TabsContext);
|
const context = useContext(TabsContext);
|
||||||
if (!context) throw new Error("TabsTrigger must be used within Tabs");
|
if (!context) throw new Error("TabsTrigger must be used within Tabs");
|
||||||
|
|
||||||
|
|
@ -80,10 +64,8 @@ export function TabsTrigger({
|
||||||
onClick={() => !disabled && context.onValueChange(value)}
|
onClick={() => !disabled && context.onValueChange(value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
isActive
|
isActive ? "bg-background text-foreground shadow-sm" : "hover:bg-muted/50",
|
||||||
? "bg-gray-700 text-foreground shadow-sm"
|
className
|
||||||
: "hover:bg-muted/50",
|
|
||||||
className,
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -104,12 +86,7 @@ export function TabsContent({ value, className, children }: TabsContentProps) {
|
||||||
if (context.value !== value) return null;
|
if (context.value !== value) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={cn("mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className)}>
|
||||||
className={cn(
|
|
||||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,7 @@ export default [
|
||||||
route("login", "routes/login.tsx"),
|
route("login", "routes/login.tsx"),
|
||||||
route("dashboard", "routes/dashboard.tsx"),
|
route("dashboard", "routes/dashboard.tsx"),
|
||||||
route("dashboard/project-management", "routes/project-management.tsx"),
|
route("dashboard/project-management", "routes/project-management.tsx"),
|
||||||
route(
|
route("dashboard/innovation-basket/process-innovation", "routes/innovation-basket.process-innovation.tsx"),
|
||||||
"dashboard/innovation-basket/process-innovation",
|
|
||||||
"routes/innovation-basket.process-innovation.tsx"
|
|
||||||
),
|
|
||||||
route(
|
|
||||||
"/dashboard/innovation-basket/digital-innovation",
|
|
||||||
"routes/digital-innovation-page.tsx"
|
|
||||||
),
|
|
||||||
route("projects", "routes/projects.tsx"),
|
route("projects", "routes/projects.tsx"),
|
||||||
route("dashboard/ecosystem", "routes/ecosystem.tsx"),
|
route("dashboard/ecosystem", "routes/ecosystem.tsx"),
|
||||||
route("404", "routes/404.tsx"),
|
route("404", "routes/404.tsx"),
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { ProtectedRoute } from "~/components/auth/protected-route";
|
|
||||||
import DigitalInnovationPage from "~/components/dashboard/project-management/digital-innovation-page";
|
|
||||||
|
|
||||||
export function meta() {
|
|
||||||
return [
|
|
||||||
{ title: "نوآوری در فرآیند - سیستم مدیریت فناوری و نوآوری" },
|
|
||||||
{ name: "description", content: "مدیریت پروژههای نوآوری در فرآیند" },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ProcessInnovation() {
|
|
||||||
return (
|
|
||||||
<ProtectedRoute requireAuth={true}>
|
|
||||||
<DigitalInnovationPage />
|
|
||||||
</ProtectedRoute>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
6859
package-lock.json
generated
6859
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -4,7 +4,7 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "react-router build",
|
"build": "react-router build",
|
||||||
"dev": "react-router dev",
|
"dev": "react-router dev --port 3000",
|
||||||
"start": "react-router-serve ./build/server/index.js",
|
"start": "react-router-serve ./build/server/index.js",
|
||||||
"typecheck": "react-router typegen && tsc"
|
"typecheck": "react-router typegen && tsc"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user