main-page #4
|
|
@ -109,7 +109,7 @@ This document describes the exact implementation of the login page based on the
|
|||
داشبورد مدیریت فناوری و نوآوری
|
||||
</h2>
|
||||
<p className="text-gray-300 text-sm font-persian leading-relaxed">
|
||||
لطفاً نام کاربری و پسورد خود را وارد فهرست خواسته شده وارد
|
||||
لطفاً نام کاربری و کلمه عبور خود را وارد فهرست خواسته شده وارد
|
||||
<br />
|
||||
فرمایید.
|
||||
</p>
|
||||
|
|
|
|||
241
app/components/dashboard/d3-image-info.tsx
Normal file
241
app/components/dashboard/d3-image-info.tsx
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
"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>
|
||||
);
|
||||
}
|
||||
74
app/components/dashboard/dashboard-custom-bar-chart.tsx
Normal file
74
app/components/dashboard/dashboard-custom-bar-chart.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,39 +1,822 @@
|
|||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { DashboardLayout } from "./layout";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { Progress } from "~/components/ui/progress";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
LineChart,
|
||||
Line,
|
||||
} from "recharts";
|
||||
import apiService from "~/lib/api";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
Calendar,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Target,
|
||||
Lightbulb,
|
||||
DollarSign,
|
||||
Minus,
|
||||
CheckCircle,
|
||||
BookOpen,
|
||||
} from "lucide-react";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
|
||||
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
||||
import { DashboardCustomBarChart } from "./dashboard-custom-bar-chart";
|
||||
import { InteractiveBarChart } from "./interactive-bar-chart";
|
||||
import { D3ImageInfo } from "./d3-image-info";
|
||||
import {
|
||||
Label,
|
||||
PolarGrid,
|
||||
PolarRadiusAxis,
|
||||
RadialBar,
|
||||
RadialBarChart,
|
||||
} from "recharts";
|
||||
import { ChartContainer } from "~/components/ui/chart";
|
||||
import { formatNumber } from "~/lib/utils";
|
||||
|
||||
export function DashboardHome() {
|
||||
const [dashboardData, setDashboardData] = useState<any | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDashboardData();
|
||||
}, []);
|
||||
|
||||
const fetchDashboardData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// First authenticate if needed
|
||||
const token = localStorage.getItem("auth_token");
|
||||
if (!token) {
|
||||
await apiService.login("inogen_admin", "123456");
|
||||
}
|
||||
|
||||
// Fetch top cards data
|
||||
const topCardsResponse = await apiService.call({
|
||||
main_page_first_function: {},
|
||||
});
|
||||
|
||||
// Fetch left section data
|
||||
const leftCardsResponse = await apiService.call({
|
||||
main_page_second_function: {},
|
||||
});
|
||||
|
||||
const topCardsResponseData = JSON.parse(topCardsResponse?.data);
|
||||
const leftCardsResponseData = JSON.parse(leftCardsResponse?.data);
|
||||
|
||||
console.log("API Responses:", {
|
||||
topCardsResponseData,
|
||||
leftCardsResponseData,
|
||||
});
|
||||
|
||||
// Use real API data structure with English keys
|
||||
const topData = topCardsResponseData || {};
|
||||
const leftData = leftCardsResponseData || {};
|
||||
const realData = {
|
||||
topData: topData,
|
||||
leftData: leftData,
|
||||
chartData: leftCardsResponseData?.chartData || [],
|
||||
};
|
||||
setDashboardData(realData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching dashboard data:", error);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "خطای نامشخص";
|
||||
setError(`خطا در بارگذاری دادهها: ${errorMessage}`);
|
||||
toast.error(`خطا در بارگذاری دادهها: ${errorMessage}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// RadialBarChart data for ideas visualization
|
||||
const getIdeasChartData = () => {
|
||||
if (!dashboardData?.topData)
|
||||
return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }];
|
||||
|
||||
const registered = parseFloat(
|
||||
dashboardData.topData.registered_innovation_technology_idea || "0",
|
||||
);
|
||||
const ongoing = parseFloat(
|
||||
dashboardData.topData.ongoing_innovation_technology_ideas || "0",
|
||||
);
|
||||
const percentage =
|
||||
registered > 0 ? Math.round((ongoing / registered) * 100) : 0;
|
||||
|
||||
return [
|
||||
{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" },
|
||||
];
|
||||
};
|
||||
|
||||
const chartData = getIdeasChartData();
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Ideas Progress",
|
||||
},
|
||||
safari: {
|
||||
label: "Safari",
|
||||
color: "var(--chart-2)",
|
||||
},
|
||||
};
|
||||
|
||||
// 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) {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="p-3 pb-0 grid grid-cols-3 gap-4 animate-pulse">
|
||||
{/* Top Cards Row */}
|
||||
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
||||
<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>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !dashboardData) {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="">
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-red-500/50">
|
||||
<CardContent className="">
|
||||
<div className="flex flex-col items-center justify-center space-y-4">
|
||||
<div className="p-4 bg-red-500/20 rounded-full">
|
||||
<CheckCircle className="w-12 h-12 text-red-400" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-red-400 text-center">
|
||||
خطا در بارگذاری دادهها
|
||||
</h3>
|
||||
<p className="text-gray-300 text-center max-w-md">
|
||||
{error ||
|
||||
"خطای نامشخص در بارگذاری دادههای داشبورد رخ داده است"}
|
||||
</p>
|
||||
<Button
|
||||
onClick={fetchDashboardData}
|
||||
variant="outline"
|
||||
className="border-red-500/50 text-red-400 hover:bg-red-500/10"
|
||||
>
|
||||
تلاش مجدد
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="p-6">
|
||||
{/* Main Content Area - Empty for now */}
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader></CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-center h-64 text-gray-500 dark:text-gray-400">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">
|
||||
<svg
|
||||
className="w-8 h-8"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
<div className="p-3 pb-0 grid grid-cols-3 gap-4">
|
||||
{/* Top Cards Row - Redesigned to match other components */}
|
||||
<div className="flex justify-between gap-6 [&>*]:w-full col-span-3">
|
||||
{/* Ideas Card */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="py-4 px-0">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
|
||||
<h3 className="text-lg font-bold text-white font-persian px-6">
|
||||
ایدههای فناوری و نوآوری
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 justify-center flex-row-reverse">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="w-full h-full max-h-20 max-w-40"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={[
|
||||
{
|
||||
browser: "ideas",
|
||||
visitors:
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea || "0",
|
||||
) > 0
|
||||
? Math.round(
|
||||
(parseFloat(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas ||
|
||||
"0",
|
||||
) /
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"1",
|
||||
)) *
|
||||
100,
|
||||
)
|
||||
: 0,
|
||||
fill: "green",
|
||||
},
|
||||
]}
|
||||
startAngle={90}
|
||||
endAngle={
|
||||
90 +
|
||||
((parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea || "0",
|
||||
) > 0
|
||||
? Math.round(
|
||||
(parseFloat(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas || "0",
|
||||
) /
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"1",
|
||||
)) *
|
||||
100,
|
||||
)
|
||||
: 0) /
|
||||
100) *
|
||||
360
|
||||
}
|
||||
innerRadius={35}
|
||||
outerRadius={55}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-red-400 last:fill-background"
|
||||
polarRadius={[38, 31]}
|
||||
/>
|
||||
</svg>
|
||||
<RadialBar
|
||||
dataKey="visitors"
|
||||
background
|
||||
cornerRadius={5}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-lg font-bold"
|
||||
>
|
||||
%
|
||||
{formatNumber(
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"0",
|
||||
) > 0
|
||||
? Math.round(
|
||||
(parseFloat(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas ||
|
||||
"0",
|
||||
) /
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"1",
|
||||
)) *
|
||||
100,
|
||||
)
|
||||
: 0,
|
||||
)}
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
<div className="font-bold font-persian text-center">
|
||||
<div className="flex flex-col justify-between items-center gap-2">
|
||||
<span className="flex font-bold items-center gap-1">
|
||||
<div className="font-light">ثبت شده :</div>
|
||||
{formatNumber(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea || "0",
|
||||
)}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 font-bold">
|
||||
<div className="font-light">در حال اجرا :</div>
|
||||
{formatNumber(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-lg font-medium font-persian mb-2">
|
||||
صفحه در دست ساخت
|
||||
</p>
|
||||
<p className="text-sm font-persian">
|
||||
محتوای این بخش به زودی اضافه خواهد شد
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Revenue Card */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="py-4 px-0">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
|
||||
<h3 className="text-lg font-bold text-white font-persian px-6">
|
||||
افزایش درآمد مبتنی بر فناوری و نوآوری
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-5xl font-bold text-green-400">
|
||||
{formatNumber(
|
||||
dashboardData.topData
|
||||
?.technology_innovation_based_revenue_growth || "0",
|
||||
)}
|
||||
</p>
|
||||
<div className="text-xs text-gray-400 font-persian">
|
||||
میلیون ریال
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-6xl font-thin text-gray-600">/</span>
|
||||
<div className="text-center">
|
||||
<p className="text-5xl font-bold text-green-400">
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
dashboardData.topData
|
||||
?.technology_innovation_based_revenue_growth_percent,
|
||||
) || "0",
|
||||
)}
|
||||
%
|
||||
</p>
|
||||
<div className="text-xs text-gray-400 font-persian">
|
||||
درصد به کل درآمد
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Cost Reduction Card */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="py-4 px-0">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
|
||||
<h3 className="text-lg font-bold text-white font-persian px-6">
|
||||
کاهش هزینه ها مبتنی بر فناوری و نوآوری
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-5xl font-bold text-green-400">
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
parseFloat(
|
||||
dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(
|
||||
/,/g,
|
||||
"",
|
||||
) || "0",
|
||||
) / 1000000,
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
<div className="text-xs text-gray-400 font-persian">
|
||||
میلیون ریال
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-6xl font-thin text-gray-600">/</span>
|
||||
<div className="text-center">
|
||||
<p className="text-5xl font-bold text-green-400">
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
dashboardData.topData
|
||||
?.technology_innovation_based_cost_reduction_percent,
|
||||
) || "0",
|
||||
)}
|
||||
%
|
||||
</p>
|
||||
<div className="text-xs text-gray-400 font-persian">
|
||||
درصد به کل هزینه
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Budget Ratio Card */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="py-4 px-0">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 pb-2">
|
||||
<h3 className="text-lg font-bold text-white font-persian px-6">
|
||||
نسبت تحقق بودجه فناوی و نوآوری
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 justify-center flex-row-reverse">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="w-full h-full max-h-20 max-w-40"
|
||||
>
|
||||
<RadialBarChart
|
||||
data={[
|
||||
{
|
||||
browser: "budget",
|
||||
visitors: parseFloat(
|
||||
dashboardData.topData
|
||||
?.innovation_budget_achievement_percent || "0",
|
||||
),
|
||||
fill: "green",
|
||||
},
|
||||
]}
|
||||
startAngle={90}
|
||||
endAngle={
|
||||
90 +
|
||||
(dashboardData.topData
|
||||
?.innovation_budget_achievement_percent /
|
||||
100) *
|
||||
360
|
||||
}
|
||||
innerRadius={35}
|
||||
outerRadius={55}
|
||||
>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-red-400 last:fill-background"
|
||||
polarRadius={[38, 31]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="visitors"
|
||||
background
|
||||
cornerRadius={5}
|
||||
/>
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-lg font-bold"
|
||||
>
|
||||
%
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
dashboardData.topData
|
||||
?.innovation_budget_achievement_percent ||
|
||||
0,
|
||||
),
|
||||
)}
|
||||
</tspan>
|
||||
</text>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
<div className="font-bold font-persian text-center">
|
||||
<div className="flex flex-col justify-between items-center gap-2">
|
||||
<span className="flex font-bold items-center gap-1">
|
||||
<div className="font-light">مصوب :</div>
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
parseFloat(
|
||||
dashboardData.topData?.approved_innovation_budget_achievement_ratio?.replace(
|
||||
/,/g,
|
||||
"",
|
||||
) || "0",
|
||||
) / 1000000000,
|
||||
),
|
||||
)}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 font-bold">
|
||||
<div className="font-light">جذب شده :</div>
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
parseFloat(
|
||||
dashboardData.topData?.allocated_innovation_budget_achievement_ratio?.replace(
|
||||
/,/g,
|
||||
"",
|
||||
) || "0",
|
||||
) / 1000000000,
|
||||
),
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Main Content with Tabs */}
|
||||
<Tabs
|
||||
defaultValue="charts"
|
||||
className="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)]"
|
||||
>
|
||||
<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>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value="charts" className="w-ful h-full">
|
||||
<InteractiveBarChart />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="canvas" className="w-ful h-full">
|
||||
<div className="p-4">
|
||||
<D3ImageInfo
|
||||
imageUrl="/main-circle.png"
|
||||
title="نمای شماتیک"
|
||||
description=":"
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Left Section - Status Cards */}
|
||||
<div className="space-y-4 row-start-2 col-span-1">
|
||||
{/* Technology Intensity */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center justify-center gap-1 px-4">
|
||||
<CardTitle className="text-white text-lg min-w-[120px]">
|
||||
شدت فناوری
|
||||
</CardTitle>
|
||||
<p className="text-base text-left">
|
||||
%
|
||||
{formatNumber(
|
||||
Math.round(
|
||||
dashboardData.leftData?.technology_intensity || 0,
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
<Progress
|
||||
value={parseFloat(
|
||||
dashboardData.leftData?.technology_intensity || "0",
|
||||
)}
|
||||
className="h-4 flex-1"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Program Status */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="py-6 px-0">
|
||||
<DashboardCustomBarChart
|
||||
title="وضعیت برنامههای فناوری و نوآوری"
|
||||
loading={loading}
|
||||
data={[
|
||||
{
|
||||
label: "اجرا شده",
|
||||
value: parseFloat(
|
||||
dashboardData?.leftData?.executed_project || "0",
|
||||
),
|
||||
color: "bg-green-400",
|
||||
},
|
||||
{
|
||||
label: "در حال اجرا",
|
||||
value: parseFloat(
|
||||
dashboardData?.leftData?.in_progress_project || "0",
|
||||
),
|
||||
color: "bg-blue-400",
|
||||
},
|
||||
{
|
||||
label: "برنامهریزی شده",
|
||||
value: parseFloat(
|
||||
dashboardData?.leftData?.planned_project || "0",
|
||||
),
|
||||
color: "bg-red-400",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Publications */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardHeader className="pb-2 border-b-2 border-gray-500/20">
|
||||
<CardTitle className="text-white text-lg">
|
||||
انتشارات فناوری و نوآوری
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-4 justify-center">
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-base">کتاب:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.printed_books_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-base">پتنت:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.registered_patents_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-yellow-400" />
|
||||
<span className="text-base">گزارش:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.published_reports_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-green-400" />
|
||||
<span className="text-base">مقاله:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.printed_articles_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Promotion */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<CardHeader className="pb-2 border-b-2 border-gray-500/20">
|
||||
<CardTitle className="text-white text-lg">
|
||||
ترویج فناوری و نوآوری
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4">
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-4 justify-center">
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-purple-400" />
|
||||
<span className="text-base">کنفرانس:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.attended_conferences_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-blue-400" />
|
||||
<span className="text-base">شرکت در رویداد:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.attended_events_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-yellow-400" />
|
||||
<span className="text-base">نمایشگاه:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.attended_exhibitions_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-green-400" />
|
||||
<span className="text-base">برگزاری رویداد:</span>
|
||||
</div>
|
||||
<span className="text-xl font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.organized_events_count || "0",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -144,145 +144,8 @@ export function DashboardHome() {
|
|||
<path d="m22 21-3-3" />
|
||||
</svg>
|
||||
</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>
|
||||
</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>
|
||||
</DashboardLayout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
<h1 className="text-xl flex items-center justify-center gap-4 font-bold text-white font-persian"><PanelLeft /> {title}</h1>
|
||||
<h1 className="text-xl flex items-center justify-center gap-4 font-bold text-white font-persian">
|
||||
<PanelLeft /> {title}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Right Section */}
|
||||
|
|
|
|||
125
app/components/dashboard/interactive-bar-chart.tsx
Normal file
125
app/components/dashboard/interactive-bar-chart.tsx
Normal file
|
|
@ -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<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,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: <CirclePause />,
|
||||
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: <Wrench />,
|
||||
|
|
@ -394,7 +392,7 @@ export function ProcessInnovationPage() {
|
|||
const fetchStats = async () => {
|
||||
try {
|
||||
setStatsLoading(true);
|
||||
const raw = await apiService.callInnovationProcess<any>({
|
||||
const raw = await apiService.call<any>({
|
||||
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() {
|
|||
<div className="grid grid-cols-2 gap-3">
|
||||
{loading || statsLoading
|
||||
? // Loading skeleton for stats cards - matching new design
|
||||
Array.from({ length: 4 }).map((_, index) => (
|
||||
<Card
|
||||
key={`skeleton-${index}`}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden"
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
|
||||
<div
|
||||
className="h-6 bg-gray-600 rounded animate-pulse"
|
||||
style={{ width: "60%" }}
|
||||
/>
|
||||
<div className="p-3 bg-emerald-500/20 rounded-full w-fit">
|
||||
<div className="w-6 h-6 bg-gray-600 rounded animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center flex-col p-1">
|
||||
<div
|
||||
className="h-8 bg-gray-600 rounded mb-1 animate-pulse"
|
||||
style={{ width: "40%" }}
|
||||
/>
|
||||
<div
|
||||
className="h-4 bg-gray-600 rounded animate-pulse"
|
||||
style={{ width: "80%" }}
|
||||
/>
|
||||
Array.from({ length: 4 }).map((_, index) => (
|
||||
<Card
|
||||
key={`skeleton-${index}`}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden"
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
|
||||
<div
|
||||
className="h-6 bg-gray-600 rounded animate-pulse"
|
||||
style={{ width: "60%" }}
|
||||
/>
|
||||
<div className="p-3 bg-emerald-500/20 rounded-full w-fit">
|
||||
<div className="w-6 h-6 bg-gray-600 rounded animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
<div className="flex items-center justify-center flex-col p-1">
|
||||
<div
|
||||
className="h-8 bg-gray-600 rounded mb-1 animate-pulse"
|
||||
style={{ width: "40%" }}
|
||||
/>
|
||||
<div
|
||||
className="h-4 bg-gray-600 rounded animate-pulse"
|
||||
style={{ width: "80%" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
: statsCards.map((card) => (
|
||||
<Card
|
||||
key={card.id}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
|
||||
<h3 className="text-lg font-bold text-white font-persian">
|
||||
{card.title}
|
||||
</h3>
|
||||
<div
|
||||
className={`p-3 gird placeitems-center rounded-full w-fit `}
|
||||
>
|
||||
{card.icon}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center flex-col p-1">
|
||||
<p
|
||||
className={`text-3xl font-bold ${card.color} mb-1`}
|
||||
>
|
||||
{card.value}
|
||||
</p>
|
||||
<p className="text-sm text-gray-300 font-persian">
|
||||
{card.description}
|
||||
</p>
|
||||
<Card
|
||||
key={card.id}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<div className="flex flex-col justify-between gap-2">
|
||||
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
|
||||
<h3 className="text-lg font-bold text-white font-persian">
|
||||
{card.title}
|
||||
</h3>
|
||||
<div
|
||||
className={`p-3 gird placeitems-center rounded-full w-fit `}
|
||||
>
|
||||
{card.icon}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
<div className="flex items-center justify-center flex-col p-1">
|
||||
<p
|
||||
className={`text-3xl font-bold ${card.color} mb-1`}
|
||||
>
|
||||
{card.value}
|
||||
</p>
|
||||
<p className="text-sm text-gray-300 font-persian">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Process Impacts Chart */}
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl w-full overflow-hidden">
|
||||
<CardContent className="p-4">
|
||||
<CardContent >
|
||||
<CustomBarChart
|
||||
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
|
||||
loading={statsLoading}
|
||||
|
|
@ -842,8 +840,8 @@ export function ProcessInnovationPage() {
|
|||
<div className="font-bold">
|
||||
{formatNumber(
|
||||
((stats.averageScore ?? 0) as number).toFixed?.(1) ??
|
||||
stats.averageScore ??
|
||||
0,
|
||||
stats.averageScore ??
|
||||
0,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -883,9 +881,9 @@ export function ProcessInnovationPage() {
|
|||
<span className="text-white font-bold font-persian">
|
||||
{selectedProjectDetails?.start_date
|
||||
? moment(
|
||||
selectedProjectDetails?.start_date,
|
||||
"YYYY-MM-DD",
|
||||
).format("YYYY/MM/DD")
|
||||
selectedProjectDetails?.start_date,
|
||||
"YYYY-MM-DD",
|
||||
).format("YYYY/MM/DD")
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -898,9 +896,9 @@ export function ProcessInnovationPage() {
|
|||
<span className="text-white font-bold font-persian">
|
||||
{selectedProjectDetails?.done_date
|
||||
? moment(
|
||||
selectedProjectDetails?.done_date,
|
||||
"YYYY-MM-DD",
|
||||
).format("YYYY/MM/DD")
|
||||
selectedProjectDetails?.done_date,
|
||||
"YYYY-MM-DD",
|
||||
).format("YYYY/MM/DD")
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
|
|
@ -265,14 +268,16 @@ export function Sidebar({
|
|||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
<button
|
||||
className={cn(
|
||||
"w-full text-right",
|
||||
// Disable pointer cursor when child is active (cannot collapse)
|
||||
item.children && item.children.some(child =>
|
||||
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}
|
||||
>
|
||||
<div
|
||||
|
|
@ -283,7 +288,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",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||
|
|
@ -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({
|
|||
</div>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Submenu */}
|
||||
{hasChildren && isExpanded && !isCollapsed && (
|
||||
<div className="mt-1 space-y-0.5">
|
||||
{item.children?.map((child) => renderMenuItem(child, level + 1))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tooltip for collapsed state */}
|
||||
{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">
|
||||
|
|
@ -361,21 +369,26 @@ export function Sidebar({
|
|||
{!isCollapsed ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<GalleryVerticalEnd
|
||||
color="black"
|
||||
color="black"
|
||||
size={32}
|
||||
strokeWidth={1}
|
||||
className="bg-green-400 p-1.5 rounded-lg"
|
||||
/>
|
||||
<div className="font-persian">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
سیستم اینوژن
|
||||
داشبورد اینوژن
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">نسخه ۰.۱</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center w-full">
|
||||
<InogenLogo size="sm" />
|
||||
<GalleryVerticalEnd
|
||||
color="black"
|
||||
size={32}
|
||||
strokeWidth={1}
|
||||
className="bg-green-400 p-1.5 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -68,15 +68,17 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
|||
setIsLoading(true);
|
||||
try {
|
||||
const [countsRes, processRes] = await Promise.all([
|
||||
apiService.callInnovationProcess<EcosystemCounts>({
|
||||
ecosystem_counts_function: {},
|
||||
apiService.call<EcosystemCounts>({
|
||||
ecosystem_count_function: {},
|
||||
}),
|
||||
apiService.callInnovationProcess<ProcessActorsResponse[]>({
|
||||
apiService.call<ProcessActorsResponse[]>({
|
||||
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) {
|
|||
</CardTitle>
|
||||
</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]">
|
||||
<CardTitle className="font-persian text-xl text-white flex justify-between px-4">
|
||||
تعداد بازیگران
|
||||
|
|
@ -495,13 +514,6 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
|
|||
</div>
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await apiService.callInnovationProcess<any[]>({
|
||||
const res = await apiService.call<any[]>({
|
||||
graph_production_function: {},
|
||||
});
|
||||
if (aborted) return;
|
||||
|
|
@ -102,7 +102,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
// Create center node
|
||||
const centerNode: Node = {
|
||||
id: "center",
|
||||
label: "مرکز اکوسیستم",
|
||||
label: "", //مرکز زیست بوم
|
||||
category: "center",
|
||||
stageid: 0,
|
||||
isCenter: true,
|
||||
|
|
@ -155,7 +155,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
|
||||
// Import apiService for the onClick handler
|
||||
const callAPI = useCallback(async (stage_id: number) => {
|
||||
return await apiService.callInnovationProcess<any>({
|
||||
return await apiService.call<any>({
|
||||
get_values_workflow_function: {
|
||||
stage_id: stage_id,
|
||||
},
|
||||
|
|
@ -317,7 +317,8 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
.attr("ry", 8)
|
||||
.attr("fill", categoryToColor[d.category] || "#94A3B8")
|
||||
.attr("stroke", "#FFFFFF")
|
||||
.attr("stroke-width", 3);
|
||||
.attr("stroke-width", 3)
|
||||
.style("pointer-events", "none");
|
||||
|
||||
// Add center image if available
|
||||
if (d.imageUrl || d.isCenter) {
|
||||
|
|
@ -399,6 +400,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
// Add hover effects
|
||||
nodeGroup
|
||||
.on("mouseenter", function (event, d) {
|
||||
if (d.isCenter) return;
|
||||
d3.select(this)
|
||||
.select(d.isCenter ? "rect" : "circle")
|
||||
.attr("filter", "url(#glow)")
|
||||
|
|
@ -419,7 +421,6 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
|
||||
// Add click handlers
|
||||
nodeGroup.on("click", async function (event, d) {
|
||||
setIsLoading(true);
|
||||
event.stopPropagation();
|
||||
|
||||
// Don't handle center node clicks
|
||||
|
|
@ -471,7 +472,6 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) {
|
|||
onNodeClick(basicDetails);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
// Update positions on simulation tick
|
||||
|
|
|
|||
351
app/components/ui/chart.tsx
Normal file
351
app/components/ui/chart.tsx
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
import * as React from "react"
|
||||
import * as RechartsPrimitive from "recharts"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode
|
||||
icon?: React.ComponentType
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
)
|
||||
}
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig
|
||||
}
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
function ChartContainer({
|
||||
id,
|
||||
className,
|
||||
children,
|
||||
config,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
config: ChartConfig
|
||||
children: React.ComponentProps<
|
||||
typeof RechartsPrimitive.ResponsiveContainer
|
||||
>["children"]
|
||||
}) {
|
||||
const uniqueId = React.useId()
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-slot="chart"
|
||||
data-chart={chartId}
|
||||
className={cn(
|
||||
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>
|
||||
{children}
|
||||
</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([, config]) => config.theme || config.color
|
||||
)
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color
|
||||
return color ? ` --color-${key}: ${color};` : null
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||
|
||||
function ChartTooltipContent({
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean
|
||||
hideIndicator?: boolean
|
||||
indicator?: "line" | "dot" | "dashed"
|
||||
nameKey?: string
|
||||
labelKey?: string
|
||||
}) {
|
||||
const { config } = useChart()
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [item] = payload
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
])
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="text-foreground font-mono font-medium tabular-nums">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend
|
||||
|
||||
function ChartLegendContent({
|
||||
className,
|
||||
hideIcon = false,
|
||||
payload,
|
||||
verticalAlign = "bottom",
|
||||
nameKey,
|
||||
}: React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean
|
||||
nameKey?: string
|
||||
}) {
|
||||
const { config } = useChart()
|
||||
|
||||
if (!payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined
|
||||
|
||||
let configLabelKey: string = key
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[
|
||||
key as keyof typeof payloadPayload
|
||||
] as string
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config]
|
||||
}
|
||||
|
||||
export {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartStyle,
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ export function CustomBarChart({
|
|||
// Loading skeleton
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={`space-y-6 ${className}`} style={{ height }}>
|
||||
<div className={`space-y-6 p-4 ${className}`} style={{ height }}>
|
||||
{title && (
|
||||
<div className="h-7 bg-gray-600 rounded animate-pulse mb-4 w-1/2"></div>
|
||||
)}
|
||||
|
|
@ -68,13 +68,15 @@ export function CustomBarChart({
|
|||
|
||||
return (
|
||||
<div className={`space-y-6 ${className}`} style={{ height }}>
|
||||
{title && (
|
||||
<h3 className="text-xl font-bold text-white font-persian text-right mb-4">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
<div className="border-b">
|
||||
{title && (
|
||||
<h3 className="text-xl font-bold text-white font-persian text-right p-4">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 px-4 pb-4">
|
||||
{data.map((item, index) => {
|
||||
const percentage =
|
||||
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
||||
|
|
@ -84,9 +86,8 @@ export function CustomBarChart({
|
|||
<div key={index} className="flex items-center gap-3">
|
||||
{/* Label */}
|
||||
<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}
|
||||
</span>
|
||||
|
|
@ -96,9 +97,8 @@ export function CustomBarChart({
|
|||
className={`flex-1 flex items-center bg-gray-700 rounded-full relative overflow-hidden ${barHeight}`}
|
||||
>
|
||||
<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={{
|
||||
width: `${Math.min(percentage, 100)}%`,
|
||||
}}
|
||||
|
|
@ -110,19 +110,18 @@ export function CustomBarChart({
|
|||
|
||||
{/* Value Label */}
|
||||
<span
|
||||
className={`font-bold text-sm min-w-[60px] text-left ${
|
||||
item.color?.includes("emerald")
|
||||
? "text-emerald-400"
|
||||
: item.color?.includes("blue")
|
||||
? "text-blue-400"
|
||||
: item.color?.includes("purple")
|
||||
? "text-purple-400"
|
||||
: item.color?.includes("red")
|
||||
? "text-red-400"
|
||||
: item.color?.includes("yellow")
|
||||
? "text-yellow-400"
|
||||
: "text-emerald-400"
|
||||
}`}
|
||||
className={`font-bold text-sm min-w-[60px] text-left ${item.color?.includes("emerald")
|
||||
? "text-emerald-400"
|
||||
: item.color?.includes("blue")
|
||||
? "text-blue-400"
|
||||
: item.color?.includes("purple")
|
||||
? "text-purple-400"
|
||||
: item.color?.includes("red")
|
||||
? "text-red-400"
|
||||
: item.color?.includes("yellow")
|
||||
? "text-yellow-400"
|
||||
: "text-emerald-400"
|
||||
}`}
|
||||
>
|
||||
{item.valuePrefix || ""}
|
||||
{formatNumber(parseFloat(displayValue))}
|
||||
|
|
|
|||
26
app/components/ui/progress.tsx
Normal file
26
app/components/ui/progress.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import * as React from "react"
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||
>(({ className, value, ...props }, ref) => (
|
||||
<ProgressPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
className="h-full w-full flex-1 bg-primary transition-all"
|
||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
))
|
||||
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||
|
||||
export { Progress }
|
||||
|
|
@ -11,7 +11,7 @@ const Table = React.forwardRef<HTMLTableElement, TableProps>(
|
|||
<div className={cn("relative w-full", containerClassName)}>
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
className={cn("w-full caption-bottom text-sm h-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
116
app/components/ui/tabs.tsx
Normal file
116
app/components/ui/tabs.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import React, { createContext, useContext, useState } from "react";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
interface TabsContextType {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const TabsContext = createContext<TabsContextType | undefined>(undefined);
|
||||
|
||||
interface TabsProps {
|
||||
defaultValue?: string;
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Tabs({
|
||||
defaultValue,
|
||||
value,
|
||||
onValueChange,
|
||||
className,
|
||||
children,
|
||||
}: TabsProps) {
|
||||
const [internalValue, setInternalValue] = useState(defaultValue || "");
|
||||
|
||||
const currentValue = value ?? internalValue;
|
||||
const handleValueChange = onValueChange ?? setInternalValue;
|
||||
|
||||
return (
|
||||
<TabsContext.Provider
|
||||
value={{ value: currentValue, onValueChange: handleValueChange }}
|
||||
>
|
||||
<div className={cn("w-full", className)}>{children}</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabsListProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabsList({ className, children }: TabsListProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabsTriggerProps {
|
||||
value: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabsTrigger({
|
||||
value,
|
||||
className,
|
||||
disabled,
|
||||
children,
|
||||
}: TabsTriggerProps) {
|
||||
const context = useContext(TabsContext);
|
||||
if (!context) throw new Error("TabsTrigger must be used within Tabs");
|
||||
|
||||
const isActive = context.value === value;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && context.onValueChange(value)}
|
||||
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",
|
||||
isActive
|
||||
? "bg-gray-700 text-foreground shadow-sm"
|
||||
: "hover:bg-muted/50",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface TabsContentProps {
|
||||
value: string;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TabsContent({ value, className, children }: TabsContentProps) {
|
||||
const context = useContext(TabsContext);
|
||||
if (!context) throw new Error("TabsContent must be used within Tabs");
|
||||
|
||||
if (context.value !== value) return null;
|
||||
|
||||
return (
|
||||
<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,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ class ApiService {
|
|||
}
|
||||
|
||||
// Innovation process function call wrapper
|
||||
public async callInnovationProcess<T = any>(payload: any) {
|
||||
public async call<T = any>(payload: any) {
|
||||
const url = "https://inogen-back.pelekan.org/api/call";
|
||||
return this.postAbsolute<T>(url, payload);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,14 @@ export default [
|
|||
route("login", "routes/login.tsx"),
|
||||
route("dashboard", "routes/dashboard.tsx"),
|
||||
route("dashboard/project-management", "routes/project-management.tsx"),
|
||||
route("dashboard/innovation-basket/process-innovation", "routes/innovation-basket.process-innovation.tsx"),
|
||||
route(
|
||||
"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("dashboard/ecosystem", "routes/ecosystem.tsx"),
|
||||
route("404", "routes/404.tsx"),
|
||||
|
|
|
|||
17
app/routes/digital-innovation-page.tsx
Normal file
17
app/routes/digital-innovation-page.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import type { Route } from "./+types/project-management";
|
|||
import { ProjectManagementPage } from "~/components/dashboard/project-management/project-management-page";
|
||||
import { ProtectedRoute } from "~/components/auth/protected-route";
|
||||
|
||||
export function meta({}: Route.MetaArgs) {
|
||||
export function meta({ }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: "مدیریت پروژهها - سیستم مدیریت فناوری و نوآوری" },
|
||||
{ name: "description", content: "مدیریت و نظارت بر پروژههای فناوری و نوآوری" },
|
||||
|
|
|
|||
6859
package-lock.json
generated
Normal file
6859
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -4,7 +4,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "react-router build",
|
||||
"dev": "react-router dev --port 3000",
|
||||
"dev": "react-router dev",
|
||||
"start": "react-router-serve ./build/server/index.js",
|
||||
"typecheck": "react-router typegen && tsc"
|
||||
},
|
||||
|
|
@ -13,8 +13,10 @@
|
|||
"@radix-ui/react-dialog": "^1.1.14",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.1",
|
||||
"@types/d3": "^7.4.3",
|
||||
|
|
@ -30,7 +32,7 @@
|
|||
"react-dom": "^19.1.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-router": "^7.7.0",
|
||||
"recharts": "^3.1.2",
|
||||
"recharts": "^2.15.4",
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
345
pnpm-lock.yaml
345
pnpm-lock.yaml
|
|
@ -20,12 +20,18 @@ importers:
|
|||
'@radix-ui/react-label':
|
||||
specifier: ^2.0.2
|
||||
version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-progress':
|
||||
specifier: ^1.1.7
|
||||
version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-select':
|
||||
specifier: ^2.2.5
|
||||
version: 2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.0.2
|
||||
version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.1.13
|
||||
version: 1.1.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@react-router/node':
|
||||
specifier: ^7.7.0
|
||||
version: 7.7.0(react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3)
|
||||
|
|
@ -72,8 +78,8 @@ importers:
|
|||
specifier: ^7.7.0
|
||||
version: 7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
recharts:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.1.1)(react@19.1.0)(redux@5.0.1)
|
||||
specifier: ^2.15.4
|
||||
version: 2.15.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
tailwind-merge:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
|
|
@ -232,6 +238,10 @@ packages:
|
|||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.28.3':
|
||||
resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
|
@ -464,6 +474,9 @@ packages:
|
|||
'@radix-ui/primitive@1.1.2':
|
||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||
|
||||
'@radix-ui/primitive@1.1.3':
|
||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7':
|
||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||
peerDependencies:
|
||||
|
|
@ -665,6 +678,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.5':
|
||||
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3':
|
||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||
peerDependencies:
|
||||
|
|
@ -678,6 +704,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-progress@1.1.7':
|
||||
resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.10':
|
||||
resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
|
||||
peerDependencies:
|
||||
|
|
@ -691,6 +730,19 @@ packages:
|
|||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.11':
|
||||
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-select@2.2.5':
|
||||
resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==}
|
||||
peerDependencies:
|
||||
|
|
@ -713,6 +765,19 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tabs@1.1.13':
|
||||
resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
|
|
@ -857,17 +922,6 @@ packages:
|
|||
peerDependencies:
|
||||
react-router: 7.8.0
|
||||
|
||||
'@reduxjs/toolkit@2.8.2':
|
||||
resolution: {integrity: sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==}
|
||||
peerDependencies:
|
||||
react: ^16.9.0 || ^17.0.0 || ^18 || ^19
|
||||
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-redux:
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.45.1':
|
||||
resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==}
|
||||
cpu: [arm]
|
||||
|
|
@ -968,12 +1022,6 @@ packages:
|
|||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@standard-schema/spec@1.0.0':
|
||||
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
|
||||
|
||||
'@standard-schema/utils@0.3.0':
|
||||
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
||||
|
||||
'@tailwindcss/node@4.1.11':
|
||||
resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==}
|
||||
|
||||
|
|
@ -1174,9 +1222,6 @@ packages:
|
|||
'@types/react@19.1.8':
|
||||
resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==}
|
||||
|
||||
'@types/use-sync-external-store@0.0.6':
|
||||
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
|
||||
|
||||
accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
|
@ -1491,6 +1536,9 @@ packages:
|
|||
detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1540,9 +1588,6 @@ packages:
|
|||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-toolkit@1.39.9:
|
||||
resolution: {integrity: sha512-9OtbkZmTA2Qc9groyA1PUNeb6knVTkvB2RSdr/LcJXDL8IdEakaxwXLHXa7VX/Wj0GmdMJPR3WhnPGhiP3E+qg==}
|
||||
|
||||
esbuild@0.25.8:
|
||||
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -1559,8 +1604,8 @@ packages:
|
|||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
|
|
@ -1574,6 +1619,10 @@ packages:
|
|||
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
||||
fast-equals@5.2.2:
|
||||
resolution: {integrity: sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
fdir@6.4.6:
|
||||
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
||||
peerDependencies:
|
||||
|
|
@ -1677,9 +1726,6 @@ packages:
|
|||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
immer@10.1.1:
|
||||
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
|
|
@ -1800,6 +1846,10 @@ packages:
|
|||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
|
|
@ -1922,6 +1972,10 @@ packages:
|
|||
resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -1994,6 +2048,9 @@ packages:
|
|||
resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
|
@ -2022,20 +2079,11 @@ packages:
|
|||
react: '>=16'
|
||||
react-dom: '>=16'
|
||||
|
||||
react-is@19.1.1:
|
||||
resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==}
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
react-redux@9.2.0:
|
||||
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.2.25 || ^19
|
||||
react: ^18.0 || ^19
|
||||
redux: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
redux:
|
||||
optional: true
|
||||
react-is@18.3.1:
|
||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||
|
||||
react-refresh@0.14.2:
|
||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||
|
|
@ -2071,6 +2119,12 @@ packages:
|
|||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-smooth@4.0.4:
|
||||
resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-style-singleton@2.2.3:
|
||||
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -2081,6 +2135,12 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-transition-group@4.4.5:
|
||||
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
|
||||
peerDependencies:
|
||||
react: '>=16.6.0'
|
||||
react-dom: '>=16.6.0'
|
||||
|
||||
react@19.1.0:
|
||||
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -2089,24 +2149,15 @@ packages:
|
|||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
recharts@3.1.2:
|
||||
resolution: {integrity: sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g==}
|
||||
engines: {node: '>=18'}
|
||||
recharts-scale@0.4.5:
|
||||
resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==}
|
||||
|
||||
recharts@2.15.4:
|
||||
resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
redux-thunk@3.1.0:
|
||||
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
|
||||
peerDependencies:
|
||||
redux: ^5.0.0
|
||||
|
||||
redux@5.0.1:
|
||||
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
|
||||
|
||||
reselect@5.1.1:
|
||||
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||
|
||||
retry@0.12.0:
|
||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||
|
|
@ -2312,11 +2363,6 @@ packages:
|
|||
'@types/react':
|
||||
optional: true
|
||||
|
||||
use-sync-external-store@1.5.0:
|
||||
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
utils-merge@1.0.1:
|
||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
|
@ -2340,8 +2386,8 @@ packages:
|
|||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
victory-vendor@37.3.6:
|
||||
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
|
||||
victory-vendor@36.9.2:
|
||||
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
|
||||
|
||||
vite-node@3.2.4:
|
||||
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
|
||||
|
|
@ -2591,6 +2637,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/runtime@7.28.3': {}
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
|
|
@ -2776,6 +2824,8 @@ snapshots:
|
|||
|
||||
'@radix-ui/primitive@1.1.2': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.3': {}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
|
|
@ -2978,6 +3028,16 @@ snapshots:
|
|||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0)
|
||||
|
|
@ -2987,6 +3047,16 @@ snapshots:
|
|||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-progress@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
|
@ -3004,6 +3074,23 @@ snapshots:
|
|||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-select@2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/number': 1.1.1
|
||||
|
|
@ -3040,6 +3127,22 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
|
||||
'@radix-ui/react-tabs@1.1.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.8)
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)':
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
|
@ -3191,18 +3294,6 @@ snapshots:
|
|||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@reduxjs/toolkit@2.8.2(react-redux@9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
'@standard-schema/utils': 0.3.0
|
||||
immer: 10.1.1
|
||||
redux: 5.0.1
|
||||
redux-thunk: 3.1.0(redux@5.0.1)
|
||||
reselect: 5.1.1
|
||||
optionalDependencies:
|
||||
react: 19.1.0
|
||||
react-redux: 9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1)
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.45.1':
|
||||
optional: true
|
||||
|
||||
|
|
@ -3263,10 +3354,6 @@ snapshots:
|
|||
'@rollup/rollup-win32-x64-msvc@4.45.1':
|
||||
optional: true
|
||||
|
||||
'@standard-schema/spec@1.0.0': {}
|
||||
|
||||
'@standard-schema/utils@0.3.0': {}
|
||||
|
||||
'@tailwindcss/node@4.1.11':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
|
|
@ -3471,8 +3558,6 @@ snapshots:
|
|||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/use-sync-external-store@0.0.6': {}
|
||||
|
||||
accepts@1.3.8:
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
|
|
@ -3795,6 +3880,11 @@ snapshots:
|
|||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
csstype: 3.1.3
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
|
|
@ -3832,8 +3922,6 @@ snapshots:
|
|||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-toolkit@1.39.9: {}
|
||||
|
||||
esbuild@0.25.8:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.8
|
||||
|
|
@ -3869,7 +3957,7 @@ snapshots:
|
|||
|
||||
etag@1.8.1: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
eventemitter3@4.0.7: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
|
|
@ -3911,6 +3999,8 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
fast-equals@5.2.2: {}
|
||||
|
||||
fdir@6.4.6(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
|
@ -4017,8 +4107,6 @@ snapshots:
|
|||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
immer@10.1.1: {}
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
internmap@2.0.3: {}
|
||||
|
|
@ -4100,6 +4188,10 @@ snapshots:
|
|||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
loose-envify@1.4.0:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
|
|
@ -4207,6 +4299,8 @@ snapshots:
|
|||
npm-package-arg: 10.1.0
|
||||
semver: 7.7.2
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
on-finished@2.3.0:
|
||||
|
|
@ -4257,6 +4351,12 @@ snapshots:
|
|||
err-code: 2.0.3
|
||||
retry: 0.12.0
|
||||
|
||||
prop-types@15.8.1:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
proxy-addr@2.0.7:
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
|
|
@ -4287,16 +4387,9 @@ snapshots:
|
|||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
react-is@19.1.1: {}
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-redux@9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1):
|
||||
dependencies:
|
||||
'@types/use-sync-external-store': 0.0.6
|
||||
react: 19.1.0
|
||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
redux: 5.0.1
|
||||
react-is@18.3.1: {}
|
||||
|
||||
react-refresh@0.14.2: {}
|
||||
|
||||
|
|
@ -4327,6 +4420,14 @@ snapshots:
|
|||
optionalDependencies:
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
react-smooth@4.0.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
fast-equals: 5.2.2
|
||||
prop-types: 15.8.1
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
react-transition-group: 4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
|
||||
react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0):
|
||||
dependencies:
|
||||
get-nonce: 1.0.1
|
||||
|
|
@ -4335,37 +4436,35 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
|
||||
react-transition-group@4.4.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
dom-helpers: 5.2.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
react@19.1.0: {}
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
recharts@3.1.2(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.1.1)(react@19.1.0)(redux@5.0.1):
|
||||
recharts-scale@0.4.5:
|
||||
dependencies:
|
||||
'@reduxjs/toolkit': 2.8.2(react-redux@9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1))(react@19.1.0)
|
||||
clsx: 2.1.1
|
||||
decimal.js-light: 2.5.1
|
||||
es-toolkit: 1.39.9
|
||||
eventemitter3: 5.0.1
|
||||
immer: 10.1.1
|
||||
|
||||
recharts@2.15.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
eventemitter3: 4.0.7
|
||||
lodash: 4.17.21
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
react-is: 19.1.1
|
||||
react-redux: 9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1)
|
||||
reselect: 5.1.1
|
||||
react-is: 18.3.1
|
||||
react-smooth: 4.0.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
recharts-scale: 0.4.5
|
||||
tiny-invariant: 1.3.3
|
||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||
victory-vendor: 37.3.6
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- redux
|
||||
|
||||
redux-thunk@3.1.0(redux@5.0.1):
|
||||
dependencies:
|
||||
redux: 5.0.1
|
||||
|
||||
redux@5.0.1: {}
|
||||
|
||||
reselect@5.1.1: {}
|
||||
victory-vendor: 36.9.2
|
||||
|
||||
retry@0.12.0: {}
|
||||
|
||||
|
|
@ -4587,10 +4686,6 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@types/react': 19.1.8
|
||||
|
||||
use-sync-external-store@1.5.0(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
|
||||
utils-merge@1.0.1: {}
|
||||
|
||||
valibot@0.41.0(typescript@5.8.3):
|
||||
|
|
@ -4606,7 +4701,7 @@ snapshots:
|
|||
|
||||
vary@1.1.2: {}
|
||||
|
||||
victory-vendor@37.3.6:
|
||||
victory-vendor@36.9.2:
|
||||
dependencies:
|
||||
'@types/d3-array': 3.2.1
|
||||
'@types/d3-ease': 3.0.2
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user