Compare commits

..

2 Commits

Author SHA1 Message Date
MehrdadAdabi
d4fd97daaa Merge branch 'main' of http://git.sepehrdata.com/Saeed0920/inogen 2025-09-28 22:32:45 +03:30
MehrdadAdabi
b60216c71d feat: add Compliance rate 2025-09-28 22:31:52 +03:30
6 changed files with 361 additions and 193 deletions

View File

@ -12,7 +12,6 @@ import {
Zap, Zap,
} from "lucide-react"; } from "lucide-react";
import moment from "moment-jalaali"; import moment from "moment-jalaali";
import { formatNumber } from "~/lib/utils";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
@ -35,7 +34,7 @@ import {
TableRow, TableRow,
} from "~/components/ui/table"; } from "~/components/ui/table";
import apiService from "~/lib/api"; import apiService from "~/lib/api";
import { formatCurrency } from "~/lib/utils"; import { formatCurrency, formatNumber } from "~/lib/utils";
import { DashboardLayout } from "../layout"; import { DashboardLayout } from "../layout";
moment.loadPersian({ usePersianDigits: true }); moment.loadPersian({ usePersianDigits: true });
@ -370,7 +369,8 @@ export function DigitalInnovationPage() {
const scrollContainer = scrollContainerRef.current; const scrollContainer = scrollContainerRef.current;
const handleScroll = () => { const handleScroll = () => {
if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return; if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current)
return;
// Clear previous timeout // Clear previous timeout
if (scrollTimeoutRef.current) { if (scrollTimeoutRef.current) {
@ -390,7 +390,9 @@ export function DigitalInnovationPage() {
}; };
if (scrollContainer) { if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll, { passive: true }); scrollContainer.addEventListener("scroll", handleScroll, {
passive: true,
});
} }
return () => { return () => {
@ -452,14 +454,12 @@ export function DigitalInnovationPage() {
innovation_digital_function: {}, innovation_digital_function: {},
}); });
// let payload: DigitalInnovationMetrics = raw?.data; // let payload: DigitalInnovationMetrics = raw?.data;
// console.log("*-*-*-*" +payload); // console.log("*-*-*-*" +payload);
// if (typeof payload === "string") { // if (typeof payload === "string") {
// try { // try {
// payload = JSON.parse(payload).innovation_digital_function; // payload = JSON.parse(payload).innovation_digital_function;
// } catch {} // } catch {}
// } // }
@ -469,10 +469,10 @@ export function DigitalInnovationPage() {
try { try {
// مرحله اول: data رو از string به object تبدیل کن // مرحله اول: data رو از string به object تبدیل کن
const parsedData = JSON.parse(raw.data); const parsedData = JSON.parse(raw.data);
// مرحله دوم: innovation_digital_function رو که خودش string هست parse کن // مرحله دوم: innovation_digital_function رو که خودش string هست parse کن
const arr = JSON.parse(parsedData.innovation_digital_function); const arr = JSON.parse(parsedData.innovation_digital_function);
// مرحله سوم: اولین خانه آرایه رو بردار // مرحله سوم: اولین خانه آرایه رو بردار
if (Array.isArray(arr) && arr.length > 0) { if (Array.isArray(arr) && arr.length > 0) {
payload = arr[0]; payload = arr[0];
@ -482,8 +482,6 @@ export function DigitalInnovationPage() {
} }
} }
const parseNum = (v: unknown): number => { const parseNum = (v: unknown): number => {
if (v == null) return 0; if (v == null) return 0;
if (typeof v === "number") return v; if (typeof v === "number") return v;
@ -601,7 +599,7 @@ export function DigitalInnovationPage() {
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => handleProjectDetails(item)} onClick={() => handleProjectDetails(item)}
className="text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/20 p-2 h-auto cursor-pointer" className="text-pr-green hover:text-emerald-300 underline-offset-4 underline font-normal hover:bg-emerald-500/20 p-2 h-auto"
> >
جزئیات بیشتر جزئیات بیشتر
</Button> </Button>
@ -955,7 +953,7 @@ export function DigitalInnovationPage() {
شرح پروژه شرح پروژه
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="body grid grid-cols-[40%_20%_40%]"> <div className="body grid grid-cols-[40%_20%_40%] pb-6">
<div className="border-l-2 border-l-gray-600 px-6"> <div className="border-l-2 border-l-gray-600 px-6">
<span className="title text-lg font-bold"> <span className="title text-lg font-bold">
{dialogInfo?.title} {dialogInfo?.title}
@ -1070,7 +1068,7 @@ export function DigitalInnovationPage() {
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-col pr-7 gap-4"> <div className="flex flex-col px-6 gap-4">
<div className="costBoard mx-auto w-full"> <div className="costBoard mx-auto w-full">
<div className="board o border border-gray-600 rounded-xl overflow-hidden flex flex-col"> <div className="board o border border-gray-600 rounded-xl overflow-hidden flex flex-col">
<span className="title bg-[#3F415A] text-white w-full p-2.5 pr-4 "> <span className="title bg-[#3F415A] text-white w-full p-2.5 pr-4 ">

View File

@ -15,8 +15,9 @@ import moment from "moment-jalaali";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { BaseCard } from "~/components/ui/base-card"; import { BaseCard } from "~/components/ui/base-card";
import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card";
import { Checkbox } from "~/components/ui/checkbox"; import { Checkbox } from "~/components/ui/checkbox";
import { CustomBarChart } from "~/components/ui/custom-bar-chart"; import { CustomBarChart } from "~/components/ui/custom-bar-chart";
import { import {
@ -36,7 +37,6 @@ import {
import apiService from "~/lib/api"; import apiService from "~/lib/api";
import { formatNumber } from "~/lib/utils"; import { formatNumber } from "~/lib/utils";
import { DashboardLayout } from "../layout"; import { DashboardLayout } from "../layout";
import { Card , CardContent} from "~/components/ui/card";
moment.loadPersian({ usePersianDigits: true }); moment.loadPersian({ usePersianDigits: true });
interface ProcessInnovationData { interface ProcessInnovationData {
@ -176,7 +176,7 @@ export function ProcessInnovationPage() {
stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum
), ),
description: "دلار کاهش یافته", description: "دلار کاهش یافته",
icon: DollarSign , icon: DollarSign,
color: "text-emerald-400", color: "text-emerald-400",
}, },
frequentfailuresreduction: { frequentfailuresreduction: {
@ -551,7 +551,11 @@ export function ProcessInnovationPage() {
</Badge> </Badge>
); );
case "title": case "title":
return <span className="font-normal text-sm text-white">{String(value)}</span>; return (
<span className="font-normal text-sm text-white">
{String(value)}
</span>
);
case "project_status": case "project_status":
return ( return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@ -567,7 +571,10 @@ export function ProcessInnovationPage() {
); );
case "project_rating": case "project_rating":
return ( return (
<Badge variant="outline" className="text-base font-semibold text-center border-none"> <Badge
variant="outline"
className="text-base font-semibold text-center border-none"
>
{formatNumber(String(value))} {formatNumber(String(value))}
</Badge> </Badge>
); );
@ -595,7 +602,10 @@ export function ProcessInnovationPage() {
{loading || statsLoading {loading || statsLoading
? // Loading skeleton for stats cards - matching new design ? // Loading skeleton for stats cards - matching new design
Array.from({ length: 4 }).map((_, index) => ( Array.from({ length: 4 }).map((_, index) => (
<BaseCard key={`skeleton-${index}`} className="rounded-2xl overflow-hidden"> <BaseCard
key={`skeleton-${index}`}
className="rounded-2xl overflow-hidden"
>
<div className="flex flex-col justify-between gap-2"> <div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20"> <div className="flex justify-between items-center border-b-2 mx-4 border-gray-500/20">
<div <div
@ -621,7 +631,10 @@ export function ProcessInnovationPage() {
)) ))
: Object.entries(stateCard).map(([key, card]) => { : Object.entries(stateCard).map(([key, card]) => {
// map percent values for each card key // map percent values for each card key
const percentMap: Record<string, number | string | undefined> = { const percentMap: Record<
string,
number | string | undefined
> = {
productionstopsprevention: stats.percentProductionStops, productionstopsprevention: stats.percentProductionStops,
bottleneckremoval: stats.percentBottleneckRemoval, bottleneckremoval: stats.percentBottleneckRemoval,
currencyreduction: stats.percentCurrencyReduction, currencyreduction: stats.percentCurrencyReduction,
@ -640,7 +653,7 @@ export function ProcessInnovationPage() {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="text-center"> <div className="text-center">
<p className="text-3xl text-pr-green font-bold mb-1"> <p className="text-3xl text-pr-green font-bold mb-1">
{(card.value)} {card.value}
</p> </p>
<div className="text-[11px] text-[#ACACAC] font-light font-persian"> <div className="text-[11px] text-[#ACACAC] font-light font-persian">
{card.description} {card.description}
@ -846,10 +859,12 @@ export function ProcessInnovationPage() {
شرح پروژه شرح پروژه
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="space-y-4 flex justify-between text-right px-6"> <div className="space-y-4 flex justify-between text-right p-6">
{/* Project Description */} {/* Project Description */}
<div className="flex-[4] border-l-2 border-gray-600"> <div className="flex-[4] border-l-2 border-gray-600">
<h2 className="font-bold text-base">{selectedProjectDetails?.title}</h2> <h2 className="font-bold text-base">
{selectedProjectDetails?.title}
</h2>
<p className="text-white font-normal text-base font-persian px-2 mt-2"> <p className="text-white font-normal text-base font-persian px-2 mt-2">
{selectedProjectDetails?.project_description || "-"} {selectedProjectDetails?.project_description || "-"}
</p> </p>

View File

@ -1,25 +1,13 @@
import { import {
Box,
Building2,
ChevronDown, ChevronDown,
ChevronRight,
FolderKanban,
GalleryVerticalEnd, GalleryVerticalEnd,
Globe, House,
LayoutDashboard, LightbulbIcon,
Leaf, ListTodo,
Lightbulb,
LogOut, LogOut,
MonitorSmartphone, Radar,
Package,
Settings, Settings,
Star, Star,
Workflow,
DiscAlbum,
House,
ListTodo,
LightbulbIcon,
Radar
} from "lucide-react"; } from "lucide-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { Link, useLocation } from "react-router"; import { Link, useLocation } from "react-router";
@ -49,7 +37,6 @@ interface MenuItem {
} }
const menuItems: MenuItem[] = [ const menuItems: MenuItem[] = [
{ {
id: "dashboard", id: "dashboard",
label: "صفحه اصلی", label: "صفحه اصلی",
@ -123,7 +110,6 @@ const menuItems: MenuItem[] = [
icon: null, icon: null,
href: "#", // This is not a route, it opens a popup href: "#", // This is not a route, it opens a popup
}, },
]; ];
const bottomMenuItems: MenuItem[] = [ const bottomMenuItems: MenuItem[] = [
@ -172,7 +158,10 @@ export function Sidebar({
// Update header title based on current route // Update header title based on current route
// If a child route is active, use that child's label prefixed by parent label // If a child route is active, use that child's label prefixed by parent label
let activeTitle: string | undefined = undefined; let activeTitle: string | undefined = undefined;
let activeIcon: React.ComponentType<{ className?: string }> | null | undefined = undefined; let activeIcon:
| React.ComponentType<{ className?: string }>
| null
| undefined = undefined;
menuItems.forEach((item) => { menuItems.forEach((item) => {
if (item.children) { if (item.children) {
const activeChild = item.children.find( const activeChild = item.children.find(
@ -190,7 +179,10 @@ export function Sidebar({
} }
}); });
if (onTitleChange) { if (onTitleChange) {
onTitleChange({ title: activeTitle ?? "صفحه اول", icon: activeIcon ?? null }); onTitleChange({
title: activeTitle ?? "صفحه اول",
icon: activeIcon ?? null,
});
} }
}; };
@ -261,7 +253,7 @@ export function Sidebar({
<button <button
key={item.id} key={item.id}
className={cn( className={cn(
"flex items-center justify-center w-full px-2 rounded-none mt-4 transition-all duration-200 group", "flex items-center justify-center w-full px-2 rounded-none mt-4 transition-all duration-200 group"
)} )}
onClick={handleClick} onClick={handleClick}
> >
@ -271,8 +263,7 @@ export function Sidebar({
</span> </span>
</div> </div>
</button> </button>
) );
} }
return ( return (
@ -331,10 +322,10 @@ export function Sidebar({
"w-full text-right", "w-full text-right",
// Disable pointer cursor when child is active (cannot collapse) // Disable pointer cursor when child is active (cannot collapse)
item.children && item.children &&
item.children.some( item.children.some(
(child) => child.href && location.pathname === child.href (child) => child.href && location.pathname === child.href
) && ) &&
"cursor-not-allowed" "cursor-not-allowed"
)} )}
onClick={handleClick} onClick={handleClick}
> >
@ -435,7 +426,7 @@ export function Sidebar({
/> />
<div className="font-persian"> <div className="font-persian">
<div className="text-sm font-semibold text-white"> <div className="text-sm font-semibold text-white">
داشبورد مدیریت فناوری و نوآوری داشبورد مدیریت فناوری و نوآوری
</div> </div>
{/* <div className="text-xs text-gray-400">نسخه ۰.۱</div> */} {/* <div className="text-xs text-gray-400">نسخه ۰.۱</div> */}
</div> </div>

View File

@ -1,25 +1,25 @@
import React, { useEffect, useState } from "react"; import { useEffect, useReducer, useState } from "react";
import { import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import {
BarChart,
Bar, Bar,
BarChart,
CartesianGrid,
Cell,
LabelList,
ResponsiveContainer,
XAxis, XAxis,
YAxis, YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
LabelList,
Cell,
} from "recharts"; } from "recharts";
import apiService from "~/lib/api"; import { Dialog, DialogContent, DialogHeader } from "~/components/ui/dialog";
import { Skeleton } from "~/components/ui/skeleton"; import { Skeleton } from "~/components/ui/skeleton";
import apiService from "~/lib/api";
import { formatNumber } from "~/lib/utils"; import { formatNumber } from "~/lib/utils";
import { ChartContainer } from "../ui/chart"; import { ChartContainer } from "../ui/chart";
import {
DropdownMenu,
DropdownMenuButton,
DropdownMenuContent,
DropdownMenuItem,
} from "../ui/dropdown-menu";
import { TruncatedText } from "../ui/truncatedText"; import { TruncatedText } from "../ui/truncatedText";
interface StrategicAlignmentData { interface StrategicAlignmentData {
@ -28,6 +28,51 @@ interface StrategicAlignmentData {
percentage?: number; percentage?: number;
} }
interface DropDownConfig {
isOpen: boolean;
selectedValue: string;
dropDownItems: Array<string>;
}
type Action =
| { type: "OPEN" }
| { type: "CLOSE" }
| { type: "SETVALUE"; value: Array<string> }
| { type: "SELECT"; value: string };
// const DropDownItems = [
// {
// id: 0,
// key: "همه مضامین",
// Value: "همه مضامین",
// },
// {
// id: 1,
// key: "ارزش های هم افزایی نوآورانه",
// Value: "همه مضامین",
// },
// {
// id: 2,
// key: "ارزش های خودکفایی نوآوورانه",
// Value: "همه مضامین",
// },
// {
// id: 3,
// key: "ارزش های فناوری های نوین",
// Value: "همه مضامین",
// },
// {
// id: 4,
// key: "ارزش های توسعه منابع انسانی",
// Value: "همه مضامین",
// },
// {
// id: 5,
// key: "ارزش های نوآوری سبز",
// Value: "همه مضامین",
// },
// ];
interface StrategicAlignmentPopupProps { interface StrategicAlignmentPopupProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
@ -41,11 +86,10 @@ const chartConfig = {
}, },
}; };
const maxHeight = 150; const maxHeight = 150;
const barHeights = () => Math.floor(Math.random() * maxHeight); const barHeights = () => Math.floor(Math.random() * maxHeight);
const ChartSkeleton = () => ( const ChartSkeleton = () => (
<div className="flex justify-center h-96 w-full p-4"> <div className="flex justify-center h-96 w-full p-4">
{/* Chart bars */} {/* Chart bars */}
<div className=" w-full flex items-end gap-10"> <div className=" w-full flex items-end gap-10">
@ -58,7 +102,7 @@ const ChartSkeleton = () => (
</div> </div>
))} ))}
</div> </div>
{/* Left space for Y-axis label */} {/* Left space for Y-axis label */}
<div className="flex flex-col justify-between mr-2"> <div className="flex flex-col justify-between mr-2">
<Skeleton className="h-6 w-15 bg-gray-700 rounded" /> <Skeleton className="h-6 w-15 bg-gray-700 rounded" />
<Skeleton className="h-6 w-15 bg-gray-700 rounded" /> <Skeleton className="h-6 w-15 bg-gray-700 rounded" />
@ -74,6 +118,11 @@ export function StrategicAlignmentPopup({
}: StrategicAlignmentPopupProps) { }: StrategicAlignmentPopupProps) {
const [data, setData] = useState<StrategicAlignmentData[]>([]); const [data, setData] = useState<StrategicAlignmentData[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [state, dispatch] = useReducer(reducer, {
isOpen: false,
selectedValue: "همه مضامین",
dropDownItems: [],
});
useEffect(() => { useEffect(() => {
if (open) { if (open) {
@ -98,29 +147,12 @@ export function StrategicAlignmentPopup({
? JSON.parse(response.data) ? JSON.parse(response.data)
: response.data; : response.data;
const processedData = responseData setBarItems(responseData);
.map((item: any) => ({ const dropDownItems = responseData.map(
strategic_theme: item.strategic_theme || "N/A", (item: any) => item.strategic_theme
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( setDropDownValues(["همه مضامین", ...dropDownItems]);
(item: StrategicAlignmentData) => ({
...item,
percentage:
total > 0
? Math.round((item.operational_fee_sum / total) * 100)
: 0,
})
);
setData(dataWithPercentage || []);
} catch (error) { } catch (error) {
console.error("Error fetching strategic alignment data:", error); console.error("Error fetching strategic alignment data:", error);
} finally { } finally {
@ -128,19 +160,135 @@ export function StrategicAlignmentPopup({
} }
}; };
const fetchDropDownItems = async (item: string) => {
try {
if (item !== "همه مضامین") {
const response = await apiService.select({
ProcessName: "project",
OutputFields: [
"value_technology_and_innovation",
"sum(operational_fee)",
],
Conditions: [["strategic_theme", "=", item]],
GroupBy: ["value_technology_and_innovation"],
});
const responseData =
typeof response.data === "string"
? JSON.parse(response.data)
: response.data;
setBarItems(responseData);
} else fetchData();
} catch (error) {
console.error("Error fetching strategic alignment data:", error);
} finally {
setLoading(false);
}
};
function reducer(state: DropDownConfig, action: Action): DropDownConfig {
switch (action.type) {
case "OPEN":
return { ...state, isOpen: true };
case "CLOSE":
return { ...state, isOpen: false };
case "SETVALUE":
return { ...state, dropDownItems: action.value };
case "SELECT":
return { ...state, selectedValue: action.value };
default:
return state;
}
}
const toggleMenuHandler = () => {
dispatch({
type: "OPEN",
});
};
const selectItem = (item: string) => {
dispatch({
type: "SELECT",
value: item,
});
dispatch({
type: "CLOSE",
});
fetchDropDownItems(item);
};
const setDropDownValues = (items: Array<string>) => {
dispatch({
type: "SETVALUE",
value: items,
});
};
const setBarItems = (responseData: any) => {
const processedData = responseData
.map((item: any) => ({
strategic_theme:
item.strategic_theme || item.value_technology_and_innovation || "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 || []);
};
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full max-w-4xl bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] text-white border-none"> <DialogContent className="w-full max-w-4xl bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] text-white border-none">
<DialogHeader className="mb-10 py-2 w-full pb-4 border-b-2 border-gray-500/20"> <DialogHeader className="mb-10 w-full border-b-2 border-gray-500/20">
<DialogTitle className="ml-auto text-sm text-white ">میزان انطباق راهبردی</DialogTitle> <div>
<div className="flex">
<DropdownMenu
modal={true}
open={state.isOpen}
onOpenChange={toggleMenuHandler}
>
<DropdownMenuButton>{state.selectedValue}</DropdownMenuButton>
<DropdownMenuContent forceMount={true} className="w-56">
{state.dropDownItems.map((item: string, key: number) => (
<div
onClick={() => selectItem(item)}
key={`${key}-${item}`}
>
<DropdownMenuItem>{item}</DropdownMenuItem>
</div>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</DialogHeader> </DialogHeader>
{loading ? ( {loading ? (
<ChartSkeleton /> <ChartSkeleton />
) : ( ) : (
<> <>
<ResponsiveContainer width="100%" height={400}> <ResponsiveContainer width="100%" height={400}>
<ChartContainer config={chartConfig} className="aspect-auto h-96 w-full"> <ChartContainer
config={chartConfig}
className="aspect-auto h-96 w-full"
>
<BarChart <BarChart
data={data} data={data}
margin={{ left: 12, right: 12 }} margin={{ left: 12, right: 12 }}
@ -149,7 +297,7 @@ export function StrategicAlignmentPopup({
accessibilityLayer accessibilityLayer
> >
<CartesianGrid vertical={false} stroke="#475569" /> <CartesianGrid vertical={false} stroke="#475569" />
<XAxis <XAxis
dataKey="strategic_theme" dataKey="strategic_theme"
tickLine={false} tickLine={false}
axisLine={false} axisLine={false}
@ -161,11 +309,8 @@ export function StrategicAlignmentPopup({
return ( return (
<g transform={`translate(${x},${y})`}> <g transform={`translate(${x},${y})`}>
<foreignObject width={80} height={20} x={-45} y={0}> <foreignObject width={80} height={20} x={-45} y={0}>
<TruncatedText <TruncatedText maxWords={2} text={payload.value} />
maxWords={2} </foreignObject>
text={payload.value}
/>
</foreignObject>
</g> </g>
); );
}} }}
@ -179,37 +324,38 @@ export function StrategicAlignmentPopup({
tickFormatter={(value) => tickFormatter={(value) =>
`${formatNumber(Math.round(value))}` `${formatNumber(Math.round(value))}`
} }
label={{
value: "تعداد برنامه ها",
label={{ angle: -90,
value: "تعداد برنامه ها" , position: "insideLeft",
angle: -90, fill: "#94a3b8",
position: "insideLeft", fontSize: 11,
fill: "#94a3b8", offset: 0,
fontSize: 11, dy: 0,
offset: 0, style: { textAnchor: "middle" },
dy: 0, }}
style: { textAnchor: "middle" },
}}
/> />
<Bar dataKey="percentage" radius={[8, 8, 0, 0]}> <Bar dataKey="percentage" radius={[8, 8, 0, 0]}>
{data.map((entry, index) => ( {data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={chartConfig.percentage.color} /> <Cell
key={`cell-${index}`}
fill={chartConfig.percentage.color}
/>
))} ))}
<LabelList <LabelList
dataKey="percentage" dataKey="percentage"
position="top" position="top"
offset={15} offset={15}
style={{ style={{
fill: "#ffffff", fill: "#ffffff",
fontSize: "16px", fontSize: "16px",
fontWeight: "bold", fontWeight: "bold",
}} }}
formatter={(v: number) => `${formatNumber(Math.round(v))}`} formatter={(v: number) =>
`${formatNumber(Math.round(v))}`
}
/> />
</Bar> </Bar>
</BarChart> </BarChart>
</ChartContainer> </ChartContainer>

View File

@ -1,18 +1,18 @@
"use client" "use client";
import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog";
import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react";
import { X } from "lucide-react" import * as React from "react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const Dialog = DialogPrimitive.Root const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
@ -26,8 +26,8 @@ const DialogOverlay = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className
)} )}
{...props} {...props}
@ -50,8 +50,8 @@ const DialogContent = React.forwardRef<
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) ));
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({
className, className,
@ -59,13 +59,13 @@ const DialogHeader = ({
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left", "flex flex-col p-4 space-y-1.5 text-center sm:text-left",
className className
)} )}
{...props} {...props}
/> />
) );
DialogHeader.displayName = "DialogHeader" DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({ const DialogFooter = ({
className, className,
@ -78,8 +78,8 @@ const DialogFooter = ({
)} )}
{...props} {...props}
/> />
) );
DialogFooter.displayName = "DialogFooter" DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
@ -93,8 +93,8 @@ const DialogTitle = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
DialogTitle.displayName = DialogPrimitive.Title.displayName DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef< const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>, React.ElementRef<typeof DialogPrimitive.Description>,
@ -105,18 +105,18 @@ const DialogDescription = React.forwardRef<
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
DialogPortal,
DialogOverlay,
DialogClose, DialogClose,
DialogTrigger,
DialogContent, DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription, DialogDescription,
} DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};

View File

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronDown, Circle } from "lucide-react";
import { Check, ChevronRight, Circle } from "lucide-react" import * as React from "react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef< const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>, React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
@ -34,11 +34,10 @@ const DropdownMenuSubTrigger = React.forwardRef<
{...props} {...props}
> >
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)) ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@ -52,9 +51,9 @@ const DropdownMenuSubContent = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@ -65,32 +64,32 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden mt-1 rounded-xl border border-gray-500 bg-pr-gray p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
)) ));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef< const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>, React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "cursor-pointer select-none rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-gray-600 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef< const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>, React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
@ -112,9 +111,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)) ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@ -135,13 +134,13 @@ const DropdownMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
)) ));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef< const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>, React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
@ -153,8 +152,8 @@ const DropdownMenuLabel = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef< const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>, React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
@ -165,8 +164,8 @@ const DropdownMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)) ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({
className, className,
@ -177,24 +176,43 @@ const DropdownMenuShortcut = ({
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props} {...props}
/> />
) );
} };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
const DropdownMenuButton = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.Trigger
ref={ref}
className={cn(
"flex items-center justify-between gap-2 text-sm outline-none border border-gray-500 p-3 rounded-xl min-w-50 max-w-72 group",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 transition-transform duration-200 group-data-[state=open]:rotate-180" />
</DropdownMenuPrimitive.Trigger>
));
DropdownMenuButton.displayName = "DropdownMenuButton";
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger, DropdownMenuButton,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuTrigger,
} };