Compare commits
2 Commits
921afe42fa
...
d4fd97daaa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4fd97daaa | ||
|
|
b60216c71d |
|
|
@ -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 ">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user