inogen/app/components/dashboard/project-management/mange-ideas-tech-page.tsx

1272 lines
53 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 { ChevronDown, ChevronUp, RefreshCw, Eye, Star, TrendingUp, Hexagon, Download } from "lucide-react";
import { useCallback, useEffect, useRef, useState, useMemo } 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 {
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, formatNumber } from "~/lib/utils";
import { DashboardLayout } from "../layout";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "~/components/ui/chart";
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, CartesianGrid, LabelList, Cell, RadialBarChart, PolarGrid, RadialBar, PolarRadiusAxis } from "recharts";
import { BaseCard } from "~/components/ui/base-card";
import { Label } from "~/components/ui/label";
import { MetricCard } from "~/components/ui/metric-card";
interface IdeaData {
idea_title: string;
idea_registration_date: string;
idea_status: string;
increased_revenue: string;
full_name: string;
personnel_number: string;
management: string;
deputy: string;
innovator_team_members: string;
innovation_type: string;
idea_originality: string;
idea_axis: string;
idea_description: string;
idea_current_status_description: string;
idea_execution_benefits: string;
process_improvements: string;
}
interface PersonRanking {
full_name: string;
full_name_count: number;
ranking: number;
stars: number;
}
interface IdeaStatusData {
idea_status: string;
idea_status_count: number;
}
interface IdeaStatsData {
registered_innovation_technology_idea: string;
ongoing_innovation_technology_ideas: string;
increased_revenue_from_ideas: string;
increased_revenue_from_ideas_percent: string;
}
interface SortConfig {
field: string;
direction: "asc" | "desc";
}
type ColumnDef = {
key: string;
label: string;
sortable: boolean;
width: string;
};
const columns: ColumnDef[] = [
{ key: "idea_title", label: "عنوان ایده", sortable: true, width: "250px" },
{ key: "idea_registration_date", label: "تاریخ ثبت ایده", sortable: true, width: "180px" },
{ key: "idea_status", label: "وضعیت ایده", sortable: true, width: "150px" },
{ key: "increased_revenue", label: "درآمد حاصل از ایده", sortable: true, width: "180px" },
{ key: "details", label: "جزئیات بیشتر", sortable: false, width: "120px" },
];
export function ManageIdeasTechPage() {
const [ideas, setIdeas] = useState<IdeaData[]>([]);
const [loading, setLoading] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize] = useState(10);
const [hasMore, setHasMore] = useState(true);
const [totalCount, setTotalCount] = useState(0);
const [actualTotalCount, setActualTotalCount] = useState(0);
const [selectedIdea, setSelectedIdea] = useState<IdeaData | null>(null);
const [isDetailsOpen, setIsDetailsOpen] = useState(false);
const [sortConfig, setSortConfig] = useState<SortConfig>({
field: "idea_title",
direction: "asc",
});
// People ranking state
const [peopleRanking, setPeopleRanking] = useState<PersonRanking[]>([]);
const [loadingPeople, setLoadingPeople] = useState(false);
// Chart state
const [chartData, setChartData] = useState<IdeaStatusData[]>([]);
const [loadingChart, setLoadingChart] = useState(false);
// Stats state
const [statsData, setStatsData] = useState<IdeaStatsData | null>(null);
const [loadingStats, setLoadingStats] = useState(false);
const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const fetchIdeas = 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: "idea",
OutputFields: [
"idea_title",
"idea_registration_date",
"idea_status",
"increased_revenue",
"full_name",
"personnel_number",
"management",
"deputy",
"innovator_team_members",
"innovation_type",
"idea_originality",
"idea_axis",
"idea_description",
"idea_current_status_description",
"idea_execution_benefits",
"process_improvements",
],
Pagination: { PageNumber: pageToFetch, PageSize: pageSize },
Sorts: [[sortConfig.field, sortConfig.direction]],
Conditions: [],
});
if (response.state === 0) {
const dataString = response.data;
if (dataString && typeof dataString === "string") {
try {
const parsedData = JSON.parse(dataString);
if (Array.isArray(parsedData)) {
if (reset) {
setIdeas(parsedData);
setTotalCount(parsedData.length);
} else {
setIdeas((prev) => [...prev, ...parsedData]);
setTotalCount((prev) => prev + parsedData.length);
}
setHasMore(parsedData.length === pageSize);
} else {
if (reset) {
setIdeas([]);
setTotalCount(0);
}
setHasMore(false);
}
} catch (parseError) {
console.error("Error parsing idea data:", parseError);
if (reset) {
setIdeas([]);
setTotalCount(0);
}
setHasMore(false);
}
} else {
if (reset) {
setIdeas([]);
setTotalCount(0);
}
setHasMore(false);
}
} else {
toast.error(response.message || "خطا در دریافت اطلاعات ایده‌ها");
if (reset) {
setIdeas([]);
setTotalCount(0);
}
setHasMore(false);
}
} catch (error) {
console.error("Error fetching ideas:", error);
toast.error("خطا در دریافت اطلاعات ایده‌ها");
if (reset) {
setIdeas([]);
setTotalCount(0);
}
setHasMore(false);
} finally {
setLoading(false);
setLoadingMore(false);
fetchingRef.current = false;
}
};
const loadMore = useCallback(() => {
if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
setCurrentPage((prev) => prev + 1);
}
}, [hasMore, loading, loadingMore]);
useEffect(() => {
fetchIdeas(true);
fetchTotalCount();
fetchPeopleRanking();
fetchChartData();
fetchStatsData();
}, [sortConfig]);
useEffect(() => {
if (currentPage > 1) {
fetchIdeas(false);
}
}, [currentPage]);
// Infinite scroll observer with debouncing
useEffect(() => {
const scrollContainer = scrollContainerRef.current;
const handleScroll = () => {
if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return;
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
scrollTimeoutRef.current = setTimeout(() => {
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
if (scrollPercentage >= 0.95) {
loadMore();
}
}, 150);
};
if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
}
return () => {
if (scrollContainer) {
scrollContainer.removeEventListener("scroll", handleScroll);
}
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
};
}, [loadMore, hasMore, loadingMore]);
const handleSort = (field: string) => {
fetchingRef.current = false;
setSortConfig((prev) => ({
field,
direction:
prev.field === field && prev.direction === "asc" ? "desc" : "asc",
}));
setCurrentPage(1);
setIdeas([]);
setHasMore(true);
};
const fetchTotalCount = async () => {
try {
const response = await apiService.select({
ProcessName: "idea",
OutputFields: ["count(idea_title)"],
Conditions: [],
});
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]) {
setActualTotalCount(parsedData[0].idea_title_count || 0);
}
} catch (parseError) {
console.error("Error parsing count data:", parseError);
}
}
}
} catch (error) {
console.error("Error fetching total count:", error);
}
};
const fetchPeopleRanking = async () => {
try {
setLoadingPeople(true);
const response = await apiService.select({
ProcessName: "idea",
OutputFields: ["full_name", "count(full_name)"],
GroupBy: ["full_name"],
});
if (response.state === 0) {
const dataString = response.data;
if (dataString && typeof dataString === "string") {
try {
const parsedData = JSON.parse(dataString);
if (Array.isArray(parsedData)) {
// Calculate rankings and stars
const counts = parsedData.map(item => item.full_name_count);
const maxCount = Math.max(...counts);
const minCount = Math.min(...counts);
// Sort by count first (highest first)
const sortedData = parsedData.sort((a, b) => b.full_name_count - a.full_name_count);
const rankedPeople = [];
let currentRank = 1;
let sum = 1;
for (let i = 0; i < sortedData.length; i++) {
const item = sortedData[i];
// If this is not the first person and their count is different from previous
if (i > 0 && sortedData[i - 1].full_name_count !== item.full_name_count) {
currentRank = sum + 1; // New rank based on position
sum++;
}
const normalizedScore = maxCount === minCount
? 1
: (item.full_name_count - minCount) / (maxCount - minCount);
const stars = Math.max(1, Math.round(normalizedScore * 5));
rankedPeople.push({
full_name: item.full_name,
full_name_count: item.full_name_count,
ranking: currentRank,
stars: stars,
});
}
setPeopleRanking(rankedPeople);
}
} catch (parseError) {
console.error("Error parsing people ranking data:", parseError);
}
}
} else {
toast.error(response.message || "خطا در دریافت اطلاعات رتبه‌بندی افراد");
}
} catch (error) {
console.error("Error fetching people ranking:", error);
toast.error("خطا در دریافت اطلاعات رتبه‌بندی افراد");
} finally {
setLoadingPeople(false);
}
};
const fetchChartData = async () => {
try {
setLoadingChart(true);
const response = await apiService.select({
ProcessName: "idea",
OutputFields: ["idea_status", "count(idea_status)"],
GroupBy: ["idea_status"],
});
if (response.state === 0) {
const dataString = response.data;
if (dataString && typeof dataString === "string") {
try {
const parsedData: IdeaStatusData[] = JSON.parse(dataString);
if (Array.isArray(parsedData)) {
setChartData(parsedData?.reverse());
}
} catch (parseError) {
console.error("Error parsing chart data:", parseError);
}
}
} else {
toast.error(response.message || "خطا در دریافت اطلاعات نمودار");
}
} catch (error) {
console.error("Error fetching chart data:", error);
toast.error("خطا در دریافت اطلاعات نمودار");
} finally {
setLoadingChart(false);
}
};
const fetchStatsData = async () => {
try {
setLoadingStats(true);
const response = await apiService.call({
idea_page_function: {}
});
if (response.state === 0) {
const dataString = response.data;
if (dataString && typeof dataString === "string") {
try {
const parsedData: IdeaStatsData = JSON.parse(dataString);
setStatsData(parsedData);
} catch (parseError) {
console.error("Error parsing stats data:", parseError);
}
}
} else {
toast.error(response.message || "خطا در دریافت آمار ایده‌ها");
}
} catch (error) {
console.error("Error fetching stats data:", error);
toast.error("خطا در دریافت آمار ایده‌ها");
} finally {
setLoadingStats(false);
}
};
const toPersianDigits = (input: string | number): string => {
const str = String(input);
const map: Record<string, string> = {
"0": "۰",
"1": "۱",
"2": "۲",
"3": "۳",
"4": "۴",
"5": "۵",
"6": "۶",
"7": "۷",
"8": "۸",
"9": "۹",
};
return str.replace(/[0-9]/g, (d) => map[d] ?? d);
};
const formatDate = (dateString: string | null) => {
if (!dateString || dateString === "null" || dateString.trim() === "") {
return "-";
}
const raw = String(dateString).trim();
const jalaliPattern = /^(\d{4})[\/](\d{1,2})[\/](\d{1,2})$/;
const jalaliMatch = raw.match(jalaliPattern);
if (jalaliMatch) {
const [, y, m, d] = jalaliMatch;
const mm = m.padStart(2, "0");
const dd = d.padStart(2, "0");
return toPersianDigits(`${y}/${mm}/${dd}`);
}
try {
const parsed = new Date(raw);
if (isNaN(parsed.getTime())) return "-";
return new Intl.DateTimeFormat("fa-IR-u-ca-persian", {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(parsed);
} catch {
return "-";
}
};
// Chart configuration for shadcn/ui
const chartConfig: ChartConfig = {
count: {
label: "تعداد",
},
};
// Color palette for idea status
// Specific colors for idea statuses
const getChartStatusColor = (status: string) => {
switch (status) {
case "اجرا شده":
return "#69C8EA";
case "تایید شده":
return "#3AEA83";
case "در حال بررسی":
return "#EAD069";
case "رد شده":
return "#F76276";
default:
return "#6B7280";
}
};
const statusColorPalette = ["#3AEA83", "#69C8EA", "#F76276", "#FFD700", "#A757FF", "#E884CE", "#C3BF8B", "#FB7185"];
// Build a mapping of status value -> color based on loaded ideas
const statusColorMap = useMemo(() => {
const map: Record<string, string> = {};
const seenStatuses = new Set<string>();
ideas.forEach((idea) => {
const status = String(idea.idea_status || "").trim();
if (status && !seenStatuses.has(status)) {
seenStatuses.add(status);
}
});
const statusArray = Array.from(seenStatuses).sort();
statusArray.forEach((status, index) => {
map[status] = statusColorPalette[index % statusColorPalette.length];
});
return map;
}, [ideas]);
const getStatusColor = (status: string) => {
const statusValue = String(status || "").trim();
return statusColorMap[statusValue] || "#6B7280";
};
const handleShowDetails = (idea: IdeaData) => {
setSelectedIdea(idea);
setIsDetailsOpen(true);
};
const renderCellContent = (item: IdeaData, column: ColumnDef) => {
const value = (item as any)[column.key];
switch (column.key) {
case "idea_title":
return (
<span className="text-sm text-white">{String(value)}</span>
);
case "idea_registration_date":
return (
<span className="text-white text-sm">
{formatDate(String(value))}
</span>
);
case "idea_status":
return (
<span className="flex items-center justify-end flex-row-reverse gap-1 w-full">
<span className="text-white text-sm">
{!!value ? String(value) : "-"}
</span>
<span
style={{
backgroundColor: getStatusColor(String(value)),
display: !value ? "none" : "block",
}}
className="inline-block w-2 h-2 rounded-full"
/>
</span>
);
case "increased_revenue":
return (
<span className="text-sm text-white w-full">
{formatCurrency(String(value || "0")).replace("ریال" , "")}
</span>
);
case "details":
return (
<Button
variant="ghost"
size="sm"
onClick={() => handleShowDetails(item)}
className="underline text-pr-green underline-offset-4 text-sm hover:bg-pr-green/20"
>
جزئیات بیشتر
</Button> );
default:
return (
<span className="text-white text-sm">
{(value && String(value)) || "-"}
</span>
);
}
};
// Custom Vertical Bar Chart Component using shadcn/ui
const VerticalBarChart = () => {
if (loadingChart) {
return (
<div className="p-6">
<div className="bg-gray-600 rounded animate-pulse w-48 mx-auto"></div>
<div className="h-40 bg-gray-700 rounded animate-pulse"></div>
</div>
);
}
if (!chartData.length) {
return (
<div className="p-6 text-center">
<h3 className="text-lg font-persian font-semibold text-white mb-4">وضعیت ایده ها</h3>
<p className="text-gray-400 font-persian">هیچ دادهای یافت نشد</p>
</div>
);
}
// Prepare data for recharts
const rechartData = chartData.map((item) => ({
status: item.idea_status,
count: item.idea_status_count,
fill: getChartStatusColor(item.idea_status),
}));
return (
<ResponsiveContainer width="100%">
<ChartContainer config={chartConfig} className="w-full">
<BarChart
margin={{ top : 25 ,left: 12, right: 12 }}
barGap={15}
barSize={45}
accessibilityLayer
data={rechartData} >
<CartesianGrid vertical={false} stroke="#475569" />
<XAxis
dataKey="status"
axisLine={false}
tickLine={false}
tick={{
fill: '#fff',
fontSize: 14,
fontFamily: 'inherit'
}}
interval={0}
angle={0}
tickMargin={10}
textAnchor="middle"
/>
<YAxis
tickMargin={20}
axisLine={false}
tickLine={false}
tick={{
fill: '#9CA3AF',
fontSize: 12,
fontFamily: 'inherit'
}}
tickFormatter={(value) => toPersianDigits(value)}
label={{
value: "تعداد برنامه ها" ,
angle: -90,
position: "insideLeft",
fill: "#94a3b8",
fontSize: 11,
offset: 0,
dy: 0,
style: { textAnchor: "middle" },
}}
/>
<Bar
dataKey="count"
radius={[4, 4, 0, 0]}
>
<LabelList
dataKey="count"
position="top"
offset={12}
style={{
fill: "#ffffff",
fontSize: "16px",
fontWeight: "bold",
}}
formatter={(v: number) => `${formatNumber(Math.round(v))}`}
/>
</Bar>
</BarChart>
</ChartContainer>
</ResponsiveContainer>
);
};
return (
<DashboardLayout title="مدیریت ایده های فناوری و نوآوری">
<div className="space-y-6 h-full">
<div className="grid grid-cols-1 grid-rows-2 lg:grid-cols-3 gap-4 h-full">
{/* People Ranking Table */}
<div className="lg:col-span-1">
<h3 className="text-base text-center my-4 font-persian font-bold text-foreground">
رتبه بندی نوآوران
</h3>
<Card className="bg-transparent border-none rounded-xl overflow-hidden">
<CardContent className="p-0 ">
<div className="relative max-h-[calc(100vh-200px)] overflow-auto custom-scrollbar">
<Table>
<TableHeader className="sticky top-0 z-50 bg-pr-gray">
<TableRow className="bg-pr-gray">
<TableHead className="text-center font-persian font-semibold text-white bg-pr-gray sticky top-0 z-20 w-16">
رتبه
</TableHead>
<TableHead className="text-right font-persian text-white font-semibold bg-pr-gray sticky top-0 z-20">
ایده پرداز
</TableHead>
<TableHead className="text-center font-persian font-semibold bg-pr-gray text-white sticky top-0 z-20 w-24">
امتیاز
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loadingPeople ? (
Array.from({ length: 10 }).map((_, index) => (
<TableRow key={`skeleton-${index}`} className="text-sm leading-tight h-12">
<TableCell className="text-center py-2 px-2">
<div className="w-6 h-6 bg-muted rounded-full animate-pulse mx-auto" />
</TableCell>
<TableCell className="text-right py-2 px-4">
<div className="h-4 bg-muted rounded animate-pulse w-3/4" />
</TableCell>
<TableCell className="text-center py-2 px-2">
<div className="flex items-center justify-center gap-1">
{Array.from({ length: 5 }).map((_, starIndex) => (
<div key={starIndex} className="w-3 h-3 bg-muted rounded animate-pulse" />
))}
</div>
</TableCell>
</TableRow>
))
) : peopleRanking.length === 0 ? (
<TableRow>
<TableCell colSpan={3} className="text-center py-8">
<span className="text-muted-foreground font-persian">
هیچ دادهای یافت نشد
</span>
</TableCell>
</TableRow>
) : (
peopleRanking.map((person) => (
<TableRow key={person.full_name} className="text-sm leading-tight h-10 not-last:border-b-pr-gray border-border">
<TableCell className="text-center py-2 px-2">
<div className="flex items-center justify-center text-white text-sm mx-auto">
{toPersianDigits(person.ranking)}
</div>
</TableCell>
<TableCell className="text-right py-2 px-4">
<span className="font-persian font-medium text-foreground">
{person.full_name}
</span>
</TableCell>
<TableCell className="text-center py-4 px-2">
<div className="flex mx-4 flex-row-reverse items-center justify-center gap-1">
{Array.from({ length: 5 }).map((_, starIndex) => (
<Star
key={starIndex}
className={`w-5 h-5 ${
starIndex < person.stars
? "text-pr-green fill-pr-green"
: "text-pr-gray"
}`}
/>
))}
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<div className="p-4 bg-pr-gray">
<div className="text-sm text-white font-semibold font-persian text-right mr-16">
کل افراد: {toPersianDigits(peopleRanking.length)}
</div>
</div>
</CardContent>
</Card>
</div>
{/* Main Ideas Table */}
<div className="col-span-2 row-span-1">
<h3 className="text-base text-center my-4 font-persian font-bold text-foreground">
لیست ایده ها
</h3>
<Card className="bg-transparent backdrop-blur-sm rounded-xl overflow-hidden">
<CardContent className="p-0">
<div className="relative">
<Table
containerRef={scrollContainerRef}
containerClassName="overflow-auto custom-scrollbar max-h-[calc(50vh-180px)]"
>
<TableHeader className="sticky top-0 z-50 bg-pr-gray">
<TableRow className="bg-pr-gray">
{columns.map((column) => (
<TableHead
key={column.key}
className="font-persian whitespace-nowrap text-white text-sm text-center font-semibold bg-pr-gray sticky top-0 z-20"
style={{ width: column.width }}
>
{column.sortable ? (
<button
onClick={() => handleSort(column.key)}
className="flex items-center gap-2"
>
<span>{column.label}</span>
{column.key === "increased_revenue" && (
<span className="text-[#ACACAC] text-right font-light text-[8px]">میلیون <br/>ریال</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 ? (
Array.from({ length: 20 }).map((_, index) => (
<TableRow
key={`skeleton-${index}`}
className="text-sm leading-tight h-12"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-right whitespace-nowrap border-success/20 py-2 px-4"
>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-muted rounded-full animate-pulse" />
<div
className="h-3 bg-muted rounded animate-pulse"
style={{ width: `${Math.random() * 60 + 40}%` }}
/>
</div>
</TableCell>
))}
</TableRow>
))
) : ideas.length === 0 ? (
<TableRow>
<TableCell
colSpan={columns.length}
className="text-center py-8"
>
<span className="text-muted-foreground font-persian">
هیچ ایدهای یافت نشد
</span>
</TableCell>
</TableRow>
) : (
ideas.map((idea, index) => (
<TableRow
key={`${idea.idea_title}-${index}`}
className="text-sm leading-tight h-12 not-last:border-b-[1px] border-b-pr-gray"
>
{columns.map((column) => (
<TableCell
key={column.key}
className="text-right text-sm text-white font-normal whitespace-nowrap py-2 px-4"
>
{renderCellContent(idea, column)}
</TableCell>
))}
</TableRow>
))
)}
</TableBody>
</Table>
</div>
{/* Infinite scroll trigger */}
<div ref={observerRef} className="h-auto">
{loadingMore && (
<div className="flex items-center justify-center py-2">
<div className="flex items-center gap-2">
<RefreshCw className="w-4 h-4 animate-spin text-success" />
<span className="font-persian text-muted-foreground text-sm">
</span>
</div>
</div>
)}
</div>
</CardContent>
{/* Footer */}
<div className="p-4 bg-pr-gray">
<div className="flex items-center justify-between font-semibold text-sm text-white font-persian">
<span>کل ایدهها: {toPersianDigits(actualTotalCount)}</span>
</div>
</div>
</Card>
</div>
{/* Chart Section */}
<BaseCard icon={TrendingUp} className="col-span-1 row-start-2 col-start-3 row-span-1" title="نمودار ایده‌ها">
<VerticalBarChart />
</BaseCard>
<div className="col-span-1 col-start-2 row-start-2 flex flex-col-reverse justify-end gap-2 row-span-1">
<BaseCard title="ایده‌های فناوری و نوآوری">
<div className="flex items-center gap-2 justify-center flex-row-reverse">
<ChartContainer
config={chartConfig}
className="aspect-square w-[6rem] h-auto"
>
<RadialBarChart
data={[
{
browser: "ideas",
visitors:
parseFloat(
statsData?.registered_innovation_technology_idea || "0"
) > 0
? Math.round(
(parseFloat(
statsData?.registered_innovation_technology_idea || "0",
) /
parseFloat(
statsData
?.registered_innovation_technology_idea ||
"1",
)) *
100,
)
: 0,
fill: "var(--color-green)",
},
]}
startAngle={90}
endAngle={
90 +
((parseFloat(
statsData
?.registered_innovation_technology_idea || "0",
) > 0
? Math.round(
(parseFloat(
statsData
?.ongoing_innovation_technology_ideas || "0",
) /
parseFloat(
statsData
?.registered_innovation_technology_idea ||
"1",
)) *
100,
)
: 0) /
100) *
360
}
innerRadius={35}
outerRadius={55}
>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-pr-red last:fill-[#24273A]"
polarRadius={[38, 31]}
/>
<RadialBar
dataKey="visitors"
background
cornerRadius={5}
/>
<PolarRadiusAxis
tick={false}
tickLine={false}
axisLine={false}
>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
>
<tspan
x={viewBox.cx}
y={viewBox.cy}
className="fill-foreground text-lg font-bold"
>
%
{formatNumber(
parseFloat(
statsData
?.registered_innovation_technology_idea ||
"0",
) > 0
? Math.round(
(parseFloat(
statsData
?.ongoing_innovation_technology_ideas ||
"0",
) /
parseFloat(
statsData
?.registered_innovation_technology_idea ||
"1",
)) *
100,
)
: 0,
)}
</tspan>
</text>
);
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
</ChartContainer>
<div className="font-bold font-persian text-center">
<div className="flex flex-col justify-between items-center gap-2">
<span className="flex font-bold items-center gap-1 text-base">
<div className="font-light text-sm">ثبت شده :</div>
{formatNumber(
statsData
?.registered_innovation_technology_idea || "0",
)}
</span>
<span className="flex items-center gap-1 font-bold text-base">
<div className="font-light text-sm">در حال اجرا :</div>
{formatNumber(
statsData
?.ongoing_innovation_technology_ideas || "0",
)}
</span>
</div>
</div>
</div>
</BaseCard>
<MetricCard
title="درآمد افزایش یافته"
value={statsData?.increased_revenue_from_ideas?.replaceAll("," , "") || "0"}
percentValue={statsData?.increased_revenue_from_ideas_percent}
percentLabel="درصد به کل درآمد"
/>
</div>
</div>
{/* Details Dialog */}
<Dialog open={isDetailsOpen} onOpenChange={setIsDetailsOpen}>
<DialogContent className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] max-w-6xl max-h-[90vh] overflow-y-auto">
<DialogHeader className="border-b border-gray-600/30 pb-4 mb-6">
<DialogTitle className="text-right font-persian text-xl text-white flex items-center justify-between">
<span>عنوان ایده: میکروکاتالیزورهای دما بالا</span>
</DialogTitle>
</DialogHeader>
{selectedIdea && <div className="flex w-full justify-center gap-4">
<div className="flex gap-4 flex-col text-right font-persian w-full border-l-2 border-l-pr-gray px-4 pb-4">
{/* مشخصات ایده پردازان Section */}
<div className="">
<h3 className="text-base font-bold text-white mb-2">
مشخصات ایده پردازان
</h3>
<div className="flex flex-col gap-4 mr-5">
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]"/>
<span className="text-white text-sm text-light">نام ایده پرداز:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{selectedIdea.full_name || "-"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">شماره پرسنلی:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{toPersianDigits(selectedIdea.personnel_number) || "۱۳۰۶۵۸۰۶"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">مدیریت:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{selectedIdea.management || "مدیریت توسعه"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">معاونت:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{selectedIdea.deputy || "توسعه"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2 col-span-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">اعضای تیم:</span>
</div>
<span className="text-white font-normal text-sm mr-10">
{selectedIdea.innovator_team_members || "رضا حسین پور, محمد رضا شیاطی, محمد مددی"}
</span>
</div>
</div>
</div>
{/* مشخصات ایده Section */}
<div className="">
<h3 className="text-base font-bold text-white mb-2">
مشخصات ایده
</h3>
<div className="flex flex-col gap-4 mr-5">
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">تاریخ ثبت ایده:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{formatDate(selectedIdea.idea_registration_date) || "-"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">نوع نوآوری:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{selectedIdea.innovation_type || "-"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">اصالت ایده:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{selectedIdea.idea_originality || "-"}</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light min-w-max">محور ایده:</span>
</div>
<span className="text-white font-normal text-sm mr-10">{selectedIdea.idea_axis || "-"}</span>
</div>
</div>
</div>
{/* نتایج و خروجی ها Section */}
<div>
<h3 className="text-base font-bold text-white mb-2">
نتایج و خروجی ها
</h3>
<div className="flex flex-col gap-4 mr-5">
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">درآمد حاصل:</span>
</div>
<span className="text-white text-sm font-normal mr-10">{formatNumber(selectedIdea.increased_revenue) || "-"}
<span className="text-[11px] mr-2 font-light">
میلیون ریال
</span>
</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">مقاله چاپ شده:</span>
</div>
<span className="text-white font-normal cursor-pointer text-sm flex items-center gap-2 mr-10">
<Download className="h-4 w-4" />
دانلود
</span>
</div>
<div className="grid grid-cols-3 items-center gap-2">
<div className="flex items-center gap-2">
<Hexagon className="stroke-pr-green h-5 w-5 stroke-[1px]" />
<span className="text-white text-sm text-light">پتنت ثبت شده:</span>
</div>
<span className="text-white cursor-pointer font-normal text-sm flex items-center gap-2 mr-10">
<Download className="h-4 w-4"/>
دانلود
</span>
</div>
</div>
</div>
</div>
<div className="w-full flex flex-col gap-8">
{/* شرح ایده Section */}
<div>
<h3 className="text-base font-bold text-white mb-4">
شرح ایده
</h3>
<div className="">
<p className="text-white text-sm">
{selectedIdea.idea_description ||
"-"
}
</p>
</div>
</div>
{/* شرح وضعیت موجود ایده Section */}
<div>
<h3 className="text-base font-bold text-white mb-4">
شرح وضعیت موجود ایده
</h3>
<div className="">
<p className="text-white leading-relaxed text-sm">
{selectedIdea.idea_current_status_description ||
"-"
}
</p>
</div>
</div>
{/* منافع حاصل از ایده Section */}
<div>
<h3 className="text-base font-bold text-white mb-4">
منافع حاصل از ایده
</h3>
<div>
<p className="text-white leading-relaxed text-sm">
{selectedIdea.idea_execution_benefits ||
"-"
}
</p>
</div>
</div>
{/* بهبود های فرآیندی ایده Section */}
<div>
<h3 className="text-base font-bold text-white mb-4">
بهبود های فرآیندی ایده
</h3>
<div>
<p className="text-white leading-relaxed text-sm">
{selectedIdea.process_improvements ||
"-"
}
</p>
</div>
</div>
</div>
</div>}
</DialogContent>
</Dialog>
</div>
</DashboardLayout>
);
}