diff --git a/app/components/dashboard/d3-image-info.tsx b/app/components/dashboard/d3-image-info.tsx index 4463bda..0f9b786 100644 --- a/app/components/dashboard/d3-image-info.tsx +++ b/app/components/dashboard/d3-image-info.tsx @@ -5,13 +5,17 @@ export type CompanyInfo = { id: string; imageUrl: string; name: string; - costReduction: number; // absolute value + costReduction: number; revenue?: number; capacity?: number; + costI : number, + capacityI : number, + revenueI : number, + cost : number | string, }; export type D3ImageInfoProps = { - companies: CompanyInfo[]; // exactly 6 items + companies: CompanyInfo[]; width?: number; height?: number; }; @@ -59,7 +63,7 @@ export function D3ImageInfo({ companies }: D3ImageInfoProps) {
{displayCompanies.map((company, index) => { - const gp = gridPositions.find(v => v.name === company.name) || { col: (index % 5) + 1, row: Math.floor(index / 5) + 1 }; + const gp = gridPositions.find(v => v.name === company.name) ; return ( <>
- + ); })}
diff --git a/app/components/dashboard/dashboard-home.tsx b/app/components/dashboard/dashboard-home.tsx index 81d6f95..940ba71 100644 --- a/app/components/dashboard/dashboard-home.tsx +++ b/app/components/dashboard/dashboard-home.tsx @@ -49,7 +49,9 @@ export function DashboardHome() { const [error, setError] = useState(null); // Chart and schematic data from select API const [companyChartData, setCompanyChartData] = useState< - { category: string; capacity: number; revenue: number; cost: number }[] + { category: string; capacity: number; revenue: number; cost: number , costI : number, + capacityI : number, + revenueI : number }[] >([]); const [totalIncreasedCapacity, setTotalIncreasedCapacity] = useState(0); @@ -138,7 +140,6 @@ export function DashboardHome() { const capacityPct = preCap > 0 ? (incCap / preCap) * 100 : 0; const revenuePct = preInc > 0 ? (incInc / preInc) * 100 : 0; const costPct = preFee > 0 ? (costRed / preFee) * 100 : 0; - console.log(costRed) return { category: rel, capacity: isFinite(capacityPct) ? capacityPct : 0, diff --git a/app/components/dashboard/layout.tsx b/app/components/dashboard/layout.tsx index af82e01..0d12e7f 100644 --- a/app/components/dashboard/layout.tsx +++ b/app/components/dashboard/layout.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { cn } from "~/lib/utils"; import { Sidebar } from "./sidebar"; import { Header } from "./header"; +import { StrategicAlignmentPopup } from "./strategic-alignment-popup"; interface DashboardLayoutProps { children: React.ReactNode; @@ -16,6 +17,7 @@ export function DashboardLayout({ }: DashboardLayoutProps) { const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); + const [isStrategicAlignmentPopupOpen, setIsStrategicAlignmentPopupOpen] = useState(false); const toggleSidebarCollapse = () => { setIsSidebarCollapsed(!isSidebarCollapsed); @@ -55,6 +57,7 @@ export function DashboardLayout({ isCollapsed={isSidebarCollapsed} onToggleCollapse={toggleSidebarCollapse} className="h-full flex-shrink-0 relative z-10" + onStrategicAlignmentClick={() => setIsStrategicAlignmentPopupOpen(true)} />
@@ -79,6 +82,7 @@ export function DashboardLayout({
+ ); } diff --git a/app/components/dashboard/sidebar.tsx b/app/components/dashboard/sidebar.tsx index 43a0878..4a90072 100644 --- a/app/components/dashboard/sidebar.tsx +++ b/app/components/dashboard/sidebar.tsx @@ -25,18 +25,20 @@ interface SidebarProps { isCollapsed?: boolean; onToggleCollapse?: () => void; className?: string; + onStrategicAlignmentClick?: () => void; } interface MenuItem { id: string; label: string; - icon: React.ComponentType<{ className?: string }>; + icon: React.ComponentType<{ className?: string }> | null; href?: string; children?: MenuItem[]; badge?: string | number; } const menuItems: MenuItem[] = [ + { id: "dashboard", label: "صفحه اصلی", @@ -104,6 +106,12 @@ const menuItems: MenuItem[] = [ icon: Star, href: "/dashboard/top-innovations", }, + { + id: "strategic-alignment", + label: "میزان انطباق راهبردی", + icon: null, + href: "#", // This is not a route, it opens a popup + }, ]; const bottomMenuItems: MenuItem[] = [ @@ -125,6 +133,7 @@ export function Sidebar({ isCollapsed = false, onToggleCollapse, className, + onStrategicAlignmentClick, }: SidebarProps) { const location = useLocation(); const [expandedItems, setExpandedItems] = useState([]); @@ -196,16 +205,42 @@ export function Sidebar({ const ItemIcon = item.icon; const handleClick = () => { - if (item.id === "logout") { + if (item.id === "strategic-alignment") { + onStrategicAlignmentClick?.(); + } else if (item.id === "logout") { logout(); } else if (hasChildren) { toggleExpanded(item.id); } }; + if (item.id === "strategic-alignment") { +return ( + +) + + } + return (
- {item.href && item.id !== "logout" ? ( + {item.href && item.href !== "#" ? (
void; +} + +// ✅ Chart config for shadcn/ui +const chartConfig = { + percentage: { + label: "", + color: "#3AEA83", + }, +}; + + const maxHeight = 150; + const barHeights = () => Math.floor(Math.random() * maxHeight); + +const ChartSkeleton = () => ( + +
+ {/* Chart bars */} +
+ {[...Array(9)].map((_, i) => ( +
+ +
+ ))} +
+ {/* Left space for Y-axis label */} +
+ + + + + +
+
+); +export function StrategicAlignmentPopup({ + open, + onOpenChange, +}: StrategicAlignmentPopupProps) { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (open) { + fetchData(); + } + }, [open]); + + const fetchData = async () => { + setLoading(true); + try { + const response = await apiService.select({ + ProcessName: "project", + OutputFields: [ + "strategic_theme", + "sum(operational_fee) as operational_fee_sum", + ], + GroupBy: ["strategic_theme"], + }); + + const responseData = + typeof response.data === "string" + ? JSON.parse(response.data) + : response.data; + + const processedData = responseData + .map((item: any) => ({ + strategic_theme: item.strategic_theme || "N/A", + operational_fee_sum: Math.max(0, Number(item.operational_fee_sum)), + })) + .filter((item: StrategicAlignmentData) => item.strategic_theme !== ""); + + const total = processedData.reduce( + (acc: number, item: StrategicAlignmentData) => + acc + item.operational_fee_sum, + 0 + ); + + const dataWithPercentage = processedData.map( + (item: StrategicAlignmentData) => ({ + ...item, + percentage: + total > 0 + ? Math.round((item.operational_fee_sum / total) * 100) + : 0, + }) + ); + setData(dataWithPercentage || []); + } catch (error) { + console.error("Error fetching strategic alignment data:", error); + } finally { + setLoading(false); + } + }; + + return ( + + + + میزان انطباق راهبردی + + + {loading ? ( + + ) : ( + <> + + + + + + + `${formatNumber(Math.round(value))}%` + } + label={{ + value: "تعداد برنامه ها" , + angle: -90, + position: "insideLeft", + fill: "#94a3b8", + fontSize: 14, + offset: 0, + dy: 0, + style: { textAnchor: "middle" }, + }} + /> + + + {data.map((entry, index) => ( + + ))} + `${formatNumber(Math.round(v))}%`} + /> + + + + + + + )} + + + ); +} diff --git a/app/components/ui/skeleton.tsx b/app/components/ui/skeleton.tsx new file mode 100644 index 0000000..1e29d7a --- /dev/null +++ b/app/components/ui/skeleton.tsx @@ -0,0 +1,13 @@ +import { cn } from "~/lib/utils" + +function Skeleton({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/app/components/ui/tooltip.tsx b/app/components/ui/tooltip.tsx new file mode 100644 index 0000000..fc97fa5 --- /dev/null +++ b/app/components/ui/tooltip.tsx @@ -0,0 +1,61 @@ +"use client" + +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "~/lib/utils" + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/package.json b/package.json index df3ac08..e855d04 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", "@react-router/node": "^7.7.0", "@react-router/serve": "^7.7.1", "@types/d3": "^7.4.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec1bd4e..a0691b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@radix-ui/react-tabs': specifier: ^1.1.13 version: 1.1.13(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@react-router/node': specifier: ^7.7.0 version: 7.8.2(react-router@7.8.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.9.2) @@ -755,6 +758,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + 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: @@ -3119,6 +3135,26 @@ snapshots: '@types/react': 19.1.12 '@types/react-dom': 19.1.9(@types/react@19.1.12) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.12)(react@19.1.1)': dependencies: react: 19.1.1