inogen/app/components/dashboard/project-management/digital-innovation-page.tsx
2025-09-11 20:16:42 +03:30

1139 lines
41 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
BrainCircuit,
ChevronDown,
ChevronUp,
Database,
Key,
LoaderCircle,
RefreshCw,
Sprout,
TrendingDown,
TrendingUp,
Zap,
} from "lucide-react";
import moment from "moment-jalaali";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card";
import { Checkbox } from "~/components/ui/checkbox";
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "~/components/ui/dialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import apiService from "~/lib/api";
import { formatCurrency } from "~/lib/utils";
import { DashboardLayout } from "../layout";
moment.loadPersian({ usePersianDigits: true });
interface SortConfig {
field: string;
direction: "asc" | "desc";
}
interface StatsCard {
id: string;
title: string;
value: string;
description?: string;
icon: React.ReactNode;
color: string;
}
// Raw API response interface for digital innovation metrics
interface DigitalInnovationMetrics {
count_innovation_digital_projects: string;
increased_revenue: string;
increased_revenue_percent: string;
reduce_costs: string;
reduce_costs_percent: string;
reduce_energy_consumption: string;
reduce_energy_consumption_percent: string;
resource_productivity: string;
resource_productivity_percent: string;
average_project_score?: number;
}
// Normalized interface for digital innovation stats
interface DigitalInnovationStats {
// totalDigitalProjects: number;
increasedRevenue: number;
increasedRevenuePercent: number;
reduceCosts: number;
reduceCostsPercent: number;
reduceEnergyConsumption: number;
reduceEnergyConsumptionPercent: number;
resourceProductivity: number;
resourceProductivityPercent: number;
avarageProjectScore: number;
countInnovationDigitalProjects: number;
}
enum DigitalCardLabel {
decreasCost = "کاهش هزینه‌ها",
increaseRevenue = "افزایش درآمد",
performance = "بهره‌وری منابع",
decreaseEnergy = "کاهش مصرف انرژی",
}
enum projectStatus {
propozal = "پروپوزال",
contract = "پیشنویس قرارداد",
inprogress = "در حال انجام",
stop = "متوقف شده",
mafasa = "مرحله مفاصا",
finish = "پایان یافته",
}
interface ProcessInnovationData {
WorkflowID: number;
desired_strategy: string;
digital_capability: string;
digital_competence: string;
digital_puberty_elements: string;
innovation_cost_reduction: number | string;
operational_plan: string;
originality_digital_solution: string;
project_description: string;
project_no: string;
project_rating: number | string;
project_status: string;
reduce_costs_percent: number;
title: string;
}
interface HouseItem {
index: number;
color?: string;
style?: string;
}
interface ListItem {
label: string;
development: number;
house: HouseItem[];
}
const columns = [
// { key: "select", label: "", sortable: false, width: "50px" },
{ key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" },
{ key: "title", label: "عنوان پروژه", sortable: true, width: "400px" },
{
key: "project_status",
label: "وضعیت پروژه",
sortable: true,
width: "140px",
},
{
key: "project_rating",
label: "امتیاز پروژه",
sortable: true,
width: "140px",
},
{ key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" },
];
export function DigitalInnovationPage() {
const [projects, setProjects] = useState<DigitalInnovationMetrics[]>([]);
const [loading, setLoading] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(20);
const [hasMore, setHasMore] = useState(true);
const [totalCount, setTotalCount] = useState(0);
const [actualTotalCount, setActualTotalCount] = useState(0);
const [statsLoading, setStatsLoading] = useState(false);
const [rating, setRating] = useState<ListItem[]>([]);
const [dialogInfo, setDialogInfo] = useState<ProcessInnovationData>();
const [stats, setStats] = useState<DigitalInnovationStats>({
increasedRevenue: 0,
increasedRevenuePercent: 0,
reduceCosts: 0,
reduceCostsPercent: 0,
reduceEnergyConsumption: 0,
reduceEnergyConsumptionPercent: 0,
resourceProductivity: 0,
resourceProductivityPercent: 0,
avarageProjectScore: 0,
countInnovationDigitalProjects: 0,
});
const [sortConfig, setSortConfig] = useState<SortConfig>({
field: "start_date",
direction: "asc",
});
const [selectedProjects, setSelectedProjects] = useState<Set<string>>(
new Set()
);
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
// const [avarage, setAvarage] = useState<number>(0);
const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false);
// Selection handlers
const handleSelectAll = () => {
if (selectedProjects.size === projects.length) {
setSelectedProjects(new Set());
} else {
setSelectedProjects(new Set(projects.map((p: any) => p.project_no)));
}
};
const handleProjectDetails = (project: ProcessInnovationData) => {
const model: ListItem = {
label: `فرآیند-${project.WorkflowID}`,
development: +project.project_rating,
house: [],
};
setRating([model]);
setDialogInfo(project);
setDetailsDialogOpen(true);
};
const formatNumber = (value: string | number) => {
if (!value) return "0";
const numericValue = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(numericValue)) return "0";
return new Intl.NumberFormat("fa-IR").format(numericValue);
};
const statsCards: StatsCard[] = [
{
id: "production-stops-prevention",
title: DigitalCardLabel.decreasCost,
value: formatNumber(stats.reduceCosts.toFixed?.(1) ?? stats.reduceCosts),
description: "میلیون ریال کاهش یافته",
icon: <TrendingDown />,
color: "text-emerald-400",
},
{
id: "bottleneck-removal",
title: DigitalCardLabel.increaseRevenue,
value: formatNumber(stats.increasedRevenue),
description: "میلیون ریال افزایش یافته",
icon: <TrendingUp />,
color: "text-emerald-400",
},
{
id: "currency-reduction",
title: DigitalCardLabel.performance,
value: formatNumber(
stats.resourceProductivity.toFixed?.(0) ?? stats.resourceProductivity
),
description: "هزار تن صرفه جوریی شده",
icon: <Database />,
color: "text-emerald-400",
},
{
id: "frequent-failures-reduction",
title: DigitalCardLabel.decreaseEnergy,
value: formatNumber(
stats.reduceEnergyConsumption.toFixed?.(1) ??
stats.reduceEnergyConsumption
),
description: "مگاوات کاهش یافته",
icon: <Zap />,
color: "text-emerald-400",
},
];
const fetchTable = async (reset = false) => {
if (fetchingRef.current) {
return;
}
try {
fetchingRef.current = true;
if (reset) {
setLoading(true);
setCurrentPage(1);
} else {
setLoadingMore(true);
}
const pageToFetch = reset ? 1 : currentPage;
const response = await apiService.select({
ProcessName: "project",
OutputFields: [
"project_no",
"title",
"project_status",
"project_rating",
"project_description",
"digital_competence",
"originality_digital_solution",
"digital_puberty_elements",
"digital_capability",
"operational_plan",
"desired_strategy",
"innovation_cost_reduction",
"reduce_costs_percent",
],
Sorts: [[sortConfig.field, sortConfig.direction]],
Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]],
Pagination: { PageNumber: pageToFetch, PageSize: pageSize },
});
if (response.state === 0) {
const dataString = response.data;
if (dataString && typeof dataString === "string") {
try {
const parsedData: ProcessInnovationData = JSON.parse(dataString);
if (Array.isArray(parsedData)) {
if (reset) {
setProjects(parsedData);
// calculateAverage(parsedData);
setTotalCount(parsedData.length);
} else {
setProjects((prev) => [...prev, ...parsedData]);
setTotalCount((prev) => prev + parsedData.length);
}
setHasMore(parsedData.length === pageSize);
} else {
if (reset) {
setProjects([]);
setTotalCount(0);
}
setHasMore(false);
}
} catch (parseError) {
console.error("Error parsing project data:", parseError);
if (reset) {
setProjects([]);
setTotalCount(0);
}
setHasMore(false);
}
} else {
if (reset) {
setProjects([]);
setTotalCount(0);
}
setHasMore(false);
}
} else {
toast.error(response.message || "خطا در دریافت اطلاعات پروژه‌ها");
if (reset) {
setProjects([]);
setTotalCount(0);
}
setHasMore(false);
}
} catch (error) {
console.error("Error fetching projects:", error);
toast.error("خطا در دریافت اطلاعات پروژه‌ها");
if (reset) {
setProjects([]);
setTotalCount(0);
}
setHasMore(false);
} finally {
setLoading(false);
setLoadingMore(false);
fetchingRef.current = false;
}
};
const loadMore = useCallback(() => {
if (!loadingMore && hasMore && !loading) {
setCurrentPage((prev) => prev + 1);
}
}, [loadingMore, hasMore, loading]);
useEffect(() => {
fetchTable(true);
fetchTotalCount();
fetchStats();
}, [sortConfig]);
useEffect(() => {
if (currentPage > 1) {
fetchTable(false);
}
}, [currentPage]);
useEffect(() => {
const scrollContainer = document.querySelector(".overflow-auto");
const handleScroll = () => {
if (!scrollContainer || !hasMore || loadingMore) return;
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
if (scrollPercentage >= 0.9) {
loadMore();
}
};
if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll);
}
return () => {
if (scrollContainer) {
scrollContainer.removeEventListener("scroll", handleScroll);
}
};
}, [loadMore, hasMore, loadingMore]);
const handleSort = (field: string) => {
fetchingRef.current = false;
setSortConfig((prev) => ({
field,
direction:
prev.field === field && prev.direction === "asc" ? "desc" : "asc",
}));
fetchTotalCount();
fetchStats();
setCurrentPage(1);
setProjects([]);
setHasMore(true);
};
const fetchTotalCount = async () => {
try {
const response = await apiService.select({
ProcessName: "project",
OutputFields: ["count(project_no)"],
Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]],
});
if (response.state === 0) {
const dataString = response.data;
if (dataString && typeof dataString === "string") {
try {
const parsedData = JSON.parse(dataString);
if (Array.isArray(parsedData) && parsedData[0]) {
const count = parsedData[0].project_no_count || 0;
setStats((prev) => ({ ...prev, totalProjects: count }));
}
} catch (parseError) {
console.error("Error parsing count data:", parseError);
}
}
}
} catch (error) {
console.error("Error fetching total count:", error);
}
};
// Fetch aggregated stats from backend call API (innovation_process_function)
const fetchStats = async () => {
try {
setStatsLoading(true);
const raw = await apiService.call<any>({
innovation_digital_function: {},
});
// let payload: DigitalInnovationMetrics = raw?.data;
// console.log("*-*-*-*" +payload);
// if (typeof payload === "string") {
// try {
// payload = JSON.parse(payload).innovation_digital_function;
// } catch {}
// }
let payload: DigitalInnovationMetrics | null = null;
if (raw?.data) {
try {
// مرحله اول: data رو از string به object تبدیل کن
const parsedData = JSON.parse(raw.data);
// مرحله دوم: innovation_digital_function رو که خودش string هست parse کن
const arr = JSON.parse(parsedData.innovation_digital_function);
// مرحله سوم: اولین خانه آرایه رو بردار
if (Array.isArray(arr) && arr.length > 0) {
payload = arr[0];
}
} catch (err) {
console.error("Error parsing API response:", err);
}
}
const parseNum = (v: unknown): number => {
if (v == null) return 0;
if (typeof v === "number") return v;
if (typeof v === "string") {
const cleaned = v.replace(/,/g, "").trim();
const n = parseFloat(cleaned);
return isNaN(n) ? 0 : n;
}
return 0;
};
const normalized: DigitalInnovationStats = {
increasedRevenue: parseNum(payload?.increased_revenue),
increasedRevenuePercent: parseNum(payload?.increased_revenue_percent),
reduceCosts: parseNum(payload?.reduce_costs),
reduceCostsPercent: parseNum(payload?.reduce_costs_percent),
reduceEnergyConsumption: parseNum(payload?.reduce_energy_consumption),
reduceEnergyConsumptionPercent: parseNum(
payload?.reduce_energy_consumption_percent
),
resourceProductivity: parseNum(payload?.resource_productivity),
resourceProductivityPercent: parseNum(
payload?.resource_productivity_percent
),
avarageProjectScore: parseNum(payload?.average_project_score),
countInnovationDigitalProjects: parseNum(
payload?.count_innovation_digital_projects
),
};
setActualTotalCount(normalized.countInnovationDigitalProjects);
setStats(normalized);
} catch (error) {
console.error("Error fetching stats:", error);
} finally {
setStatsLoading(false);
}
};
// const handleRefresh = () => {
// fetchingRef.current = false;
// setCurrentPage(1);
// setProjects([]);
// setHasMore(true);
// fetchTable(true);
// fetchTotalCount();
// fetchStats();
// };
const renderProgress = useMemo(() => {
const total = 10;
for (let i = 0; i < rating.length; i++) {
const currentElm = rating[i];
currentElm.house = [];
const greenBoxes = Math.floor((total * currentElm.development) / 100);
const partialPercent =
(total * currentElm.development) / 100 - greenBoxes;
for (let j = 0; j < greenBoxes; j++) {
currentElm.house.push({
index: j,
color: "!bg-emerald-400",
});
}
if (partialPercent != 0 && greenBoxes != 10)
currentElm.house.push({
index: greenBoxes + 1,
style: `linear-gradient(
to right,
oklch(76.5% 0.177 163.223) 0%,
oklch(76.5% 0.177 163.223) ${partialPercent * 100}%,
oklch(55.1% 0.027 264.364) ${partialPercent * 100}%,
oklch(55.1% 0.027 264.364) 100%
)`,
});
}
}, [rating]);
const statusColor = (status: projectStatus): any => {
let el = null;
switch (status) {
case projectStatus.contract:
el = "teal";
break;
case projectStatus.finish:
el = "info";
break;
case projectStatus.stop:
el = "warning";
break;
case projectStatus.inprogress:
el = "teal";
break;
case projectStatus.mafasa:
el = "destructive";
break;
case projectStatus.propozal:
el = "info";
}
return el;
};
const renderCellContent = (item: any, column: any) => {
const value = item[column.key as keyof ProcessInnovationData];
switch (column.key) {
case "select":
return (
<Checkbox
checked={selectedProjects.has(item.project_no)}
// onCheckedChange={() => handleSelectProject(item.project_no)}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
);
case "details":
return (
<Button
variant="ghost"
size="sm"
onClick={() => handleProjectDetails(item)}
className="text-emerald-400 hover:text-emerald-300 hover:bg-emerald-500/20 p-2 h-auto cursor-pointer"
>
جزئیات بیشتر
</Button>
);
case "amount_currency_reduction":
return (
<span className="font-medium text-emerald-400">
{formatCurrency(String(value))}
</span>
);
case "project_no":
return (
<Badge variant="outline" className="font-mono">
{String(value)}
</Badge>
);
case "title":
return <span className="font-medium text-white">{String(value)}</span>;
case "project_status":
return (
<div className="flex items-center gap-1">
<Badge
variant={statusColor(value)}
className="font-medium border-2 p-0 block w-2 h-2 rounded-full"
style={{
border: "none",
}}
></Badge>
{String(value)}
</div>
);
case "project_rating":
return (
<Badge variant="outline" className="text-lg text-center border-none">
{formatNumber(String(value))}
</Badge>
);
case "reduce_prevention_production_stops":
case "throat_removal":
case "Reduce_rate_failure":
return (
<span className="font-medium text-blue-400">
{formatNumber(String(value))}
</span>
);
default:
return <span className="text-gray-300">{String(value) || "-"}</span>;
}
};
return (
<DashboardLayout title="نوآوری دیجیتال">
<div className="p-6 space-y-4 grid justify-between gap-8 sm:grid-cols-1 xl:grid-cols-[40%_60%]">
{/* Stats Cards */}
<div className="flex flex-col gap-6 w-full mb-0">
<div className="space-y-6 w-full">
{/* Stats Grid */}
<div className="grid grid-cols-2 gap-5">
{loading
? // Loading skeleton for stats cards - matching new design
Array.from({ length: 4 }).map((_, index) => (
<Card
key={`skeleton-${index}`}
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-2xl overflow-hidden !h-full"
>
<CardContent className="p-0">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 px-6 py-4 border-gray-500/20">
<div
className="h-6 bg-gray-600 rounded animate-pulse"
style={{ width: "60%" }}
/>
<div className="bg-emerald-500/20 rounded-full w-fit">
<div className="w-6 h-6 bg-gray-600 rounded animate-pulse" />
</div>
</div>
<div className="flex items-center justify-center flex-col p-4">
<div
className="h-8 bg-gray-600 rounded mb-1 animate-pulse"
style={{ width: "40%" }}
/>
<div
className="h-4 bg-gray-600 rounded animate-pulse"
style={{ width: "80%" }}
/>
</div>
</div>
</CardContent>
</Card>
))
: statsCards.map((card) => (
<Card
key={card.id}
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
>
<CardContent className="p-0">
<div className="flex flex-col justify-between gap-2">
<div className="flex justify-between items-center border-b-2 px-6 border-gray-500/20">
<h3 className="text-lg font-bold text-white font-persian py-4">
{card.title}
</h3>
<div
className={`gird placeitems-center rounded-full w-fit`}
>
{card.icon}
</div>
</div>
<div className="flex items-center justify-center flex-col p-2 pb-4">
<p
className={`text-3xl font-bold ${card.color} mb-1`}
>
{card.value}
</p>
<p className="text-sm text-gray-300 font-persian">
{card.description}
</p>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
{/* Process Impacts Chart */}
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm rounded-lg w-full overflow-hidden h-full ">
{/* <CardContent > */}
<CustomBarChart
title="تاثیرات نوآوری دیجیتال به صورت درصد مقایسه ای"
loading={statsLoading}
height="100%"
data={[
{
label: DigitalCardLabel.decreasCost,
value: stats.reduceCostsPercent || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
{
label: DigitalCardLabel.increaseRevenue,
value: stats.increasedRevenuePercent || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
{
label: DigitalCardLabel.performance,
value: stats.resourceProductivityPercent || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
{
label: DigitalCardLabel.decreaseEnergy,
value: stats.reduceEnergyConsumptionPercent || 0,
color: "bg-emerald-400",
labelColor: "text-white",
},
]}
barHeight="h-5"
showAxisLabels={true}
/>
{/* </CardContent> */}
</Card>
</div>
{/* Data Table */}
<Card className="bg-transparent backdrop-blur-sm rounded-lg overflow-hidden w-full h-[39.7rem]">
<CardContent className="p-0">
<div className="relative h-full">
<Table containerClassName="overflow-auto custom-scrollbar w-full h-[36.8rem] ">
<TableHeader>
<TableRow className="bg-[#3F415A]">
{columns.map((column) => (
<TableHead
key={column.key}
className="text-right font-persian whitespace-nowrap text-gray-200 font-medium sticky top-0 z-20 bg-[#3F415A]"
style={{ width: column.width }}
>
{column.key === "select" ? (
<div className="flex items-center justify-center">
<Checkbox
checked={
selectedProjects.size === projects.length &&
projects.length > 0
}
onCheckedChange={handleSelectAll}
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
/>
</div>
) : column.sortable ? (
<button
onClick={() => handleSort(column.key)}
className="flex items-center gap-2"
>
<span>{column.label}</span>
{sortConfig.field === column.key ? (
sortConfig.direction === "asc" ? (
<ChevronUp className="w-4 h-4" />
) : (
<ChevronDown className="w-4 h-4" />
)
) : (
<div className="w-4 h-4" />
)}
</button>
) : (
column.label
)}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
// Skeleton loading rows (compact)
Array.from({ length: 10 }).map((_, index) => (
<TableRow
key={`skeleton-${index}`}
className="text-sm leading-tight h-8"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-right whitespace-nowrap border-emerald-500/20 py-1 px-2"
>
<div className="flex items-center gap-2">
<div className="w-2.5 h-2.5 bg-gray-600 rounded-full animate-pulse" />
<div
className="h-2.5 bg-gray-600 rounded animate-pulse"
style={{ width: `${Math.random() * 60 + 40}%` }}
/>
</div>
</TableCell>
))}
</TableRow>
))
) : projects.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-8"
>
<span className="text-gray-400 font-persian">
هیچ پروژهای یافت نشد
</span>
</TableCell>
</TableRow>
) : (
projects.map((project, index) => (
<TableRow
key={`${project.count_innovation_digital_projects}-${index}`}
className="text-sm leading-tight h-8"
>
{columns.map((column) => (
<TableCell
key={column.key}
className={`text-right whitespace-nowrap border-emerald-500/20 py-1 px-2 ${column.key === "select" ? "flex justify-center items-center" : ""}`}
>
{renderCellContent(project, column)}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* Infinite scroll trigger */}
<div ref={observerRef} className="h-auto">
{loadingMore && (
<div className="flex items-center justify-center py-1">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 animate-spin text-emerald-400" />
<span className="font-persian text-gray-300 text-xs"></span>
</div>
</div>
)}
</div>
</CardContent>
{/* Footer */}
<div className="p-2 px-4 bg-gray-700/50">
<div className="grid grid-cols-[minmax(100px,0fr)_minmax(80px,1fr)_minmax(100px,2fr)_minmax(10px,1fr)_minmax(100px,1fr)] text-sm text-gray-300 font-persian items-center ">
<div></div>
<div className="text-center text-gray-400 ">
کل پروژهها:{" "}
<span className="font-bold">
{formatNumber(actualTotalCount)}
</span>
</div>
<div className="flex flex-row-reverse">
<span className="block w-7 h-2.5 bg-violet-500 rounded-tl-xl rounded-bl-xl"></span>
<span className="block w-7 h-2.5 bg-purple-500 "></span>
<span className="block w-7 h-2.5 bg-cyan-300 "></span>
<span className="block w-7 h-2.5 bg-pink-400 rounded-tr-xl rounded-br-xl"></span>
</div>
<div className="text-center text-gray-400 py-2">
میانگین:{" "}
<span className="font-bold">
{formatNumber(
((stats.avarageProjectScore ?? 0) as number).toFixed?.(1) ??
0
)}
</span>
</div>
<div></div>
</div>
</div>
{/* <div className="p-2 px-4 bg-gray-700/50">
<div className="flex flex-row gap-4 text-sm text-gray-300 font-persian justify-between">
<div className="text-center gap-2 items-center w-1/3 pr-16">
<div className="text-base text-gray-401 mb-1">
کل پروژه ها : {formatNumber(actualTotalCount)}
</div>
</div>
<div className="flex items-center flex-row gap-4 status w-3/5 justify-center">
<div className="flex flex-row-reverse">
<span className="block w-7 h-2.5 bg-violet-500 rounded-tl-xl rounded-bl-xl"></span>
<span className="block w-7 h-2.5 bg-purple-500 "></span>
<span className="block w-7 h-2.5 bg-cyan-300 "></span>
<span className="block w-7 h-2.5 bg-pink-400 rounded-tr-xl rounded-br-xl"></span>
</div>
<div className="flex justify-center items-center gap-2">
<div className="text-base text-gray-400 mb-1">میانگین :</div>
<div className="font-bold">
{formatNumber(
((stats.avarageProjectScore ?? 0) as number).toFixed?.(
1
) ?? 0
)}
</div>
</div>
</div>
</div>
</div> */}
</Card>
</div>
{/* Project Details Dialog */}
<Dialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
<DialogContent className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] max-w-5xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-white mr-4 border-b-2 border-gray-600 pb-4 font-persian text-right">
شرح پروژه
</DialogTitle>
</DialogHeader>
<div className="body grid grid-cols-[40%_20%_40%]">
<div className="border-l-2 border-l-gray-600 px-6">
<span className="title text-lg font-bold">
{dialogInfo?.title}
</span>
<p className="p-0 py-4 pb-8 text-justify text-base">
{dialogInfo?.project_description}
</p>
<div className="details flex flex-col gap-3">
<span className="text-lg font-bold">ویژگی های اصلی پروژه:</span>
<div className="flex flex-col gap-3">
<div className="flex justify-between">
<div className="flex gap-1.5">
<Key size={"1.2rem"} className="text-emerald-400" />
<span className="text-sm text-gray-300">
شایستگی دیجیتال:
</span>
</div>
<span className="text-sm text-gray-100">
{dialogInfo?.digital_capability}
</span>
</div>
<div className="flex justify-between">
<div className="flex gap-1.5">
<Sprout size={"1.2rem"} className="text-emerald-400" />
<span className="text-sm text-gray-300">
اصالت راهکار دیجیتال:
</span>
</div>
<span className="text-sm text-gray-100">
{dialogInfo?.digital_competence}
</span>
</div>
<div className="flex justify-between">
<div className="flex gap-1.5">
<BrainCircuit
size={"1.2rem"}
className="text-emerald-400"
/>
<span className="text-sm text-gray-300">
المان های بلوغ دیجیتال:
</span>
</div>
<span className="text-sm text-gray-100">
{dialogInfo?.digital_puberty_elements}
</span>
</div>
</div>
</div>
</div>
<div className="digitalAbilityDevelopment flex flex-col gap-10 border-l-2 border-l-gray-600 px-5">
<div className="flex flex-col gap-4">
<span className="text-md font-bold">
توسعه قابلیت های دیجیتال:{" "}
</span>
<div className="flex flex-col gap-2">
<div className="flex gap-1.5">
<LoaderCircle
size={"1.2rem"}
className="text-emerald-400"
/>
<span className="text-sm">
{dialogInfo?.digital_capability}
</span>
</div>
</div>
</div>
<div className="flex flex-col gap-4">
<span className="text-md font-bold">
برنامه های عملیاتی مرتبط:
</span>
<div className="flex flex-col gap-2">
<div className="flex gap-1.5">
<LoaderCircle
size={"1.2rem"}
className="text-emerald-400"
/>
<span className="text-sm">
{dialogInfo?.operational_plan}
</span>
</div>
</div>
</div>
<div className="flex flex-col gap-4">
<span className="text-md font-bold">
استراتژی های مورد نظر:
</span>
<div className="flex flex-col gap-2">
<div className="flex gap-1.5">
<LoaderCircle
size={"1.2rem"}
className="text-emerald-400"
/>
<span className="text-sm">
{dialogInfo?.desired_strategy}
</span>
</div>
{/* <div className="flex gap-1.5">
<LoaderCircle
size={"1.2rem"}
className="text-emerald-400"
/>
<span>قابلیت شماره یک </span>
</div> */}
{/* <div className="flex gap-1.5">
<LoaderCircle
size={"1.2rem"}
className="text-emerald-400"
/>
<span>قابلیت شماره یک </span>
</div> */}
</div>
</div>
</div>
<div className="flex flex-col pr-7 gap-4">
<div className="costBoard mx-auto w-full">
<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>
<div className="content p-4 flex flex-row-reverse gap-10 justify-center items-center">
<div className="flex flex-col gap-1">
<span className="text-emerald-400 font-bold text-3xl">
%{" "}
{formatNumber(
(
Math.round(
dialogInfo?.reduce_costs_percent! * 100
) / 100
).toFixed(2)
)}
</span>
<span className="text-gray-500 text-sm font-normal">
درصد به کل هزینه ها
</span>
</div>
<b className="w-0.5 h-9 bg-gray-600 rotate-[35deg] rounded-full" />
<div className="flex flex-col gap-1">
<span className="text-emerald-400 text-3xl font-bold">
{formatNumber(+dialogInfo?.innovation_cost_reduction!)}
</span>
<span className="text-gray-500 text-sm font-normal">
میلیون ریال
</span>
</div>
</div>
</div>
</div>
<div className="processTabel rounded-tr-lg rounded-tl-lg overflow-hidden box-border flex flex-col gap-3 w-full mx-auto">
<div className="text- header bg-[#3F415A] flex justify-between p-2">
<span>عنوان فرآیند</span>
<span>درصد پیشرفت</span>
</div>
<div className="rows flex flex-col gap-2">
{rating.map((el, index) => {
return (
<div
className="row border-b-1 border-b-[#3F415A] pb-2 px-2 flex justify-between items-center last:border-none"
key={`rating-${index}`}
>
<span className="pName">{el.label}</span>
<div
className="ProgressBar flex flex-row gap-1 rounded-md overflow-hidden"
dir="ltr"
>
{Array.from({ length: 10 }, (_, i) => {
return (
<span
className={`block bg-gray-500 w-1.5 h-6 ${el.house[i]?.color}`}
style={{
background: el.house[i]?.style,
}}
></span>
);
})}
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</DashboardLayout>
);
}
export default DigitalInnovationPage;