From efa46a02c257b28897de9f4b239036f34c5524cd Mon Sep 17 00:00:00 2001 From: MehrdadAdabi <126083584+mehrdadAdabi@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:30:13 +0330 Subject: [PATCH] fix: change date picker logic to another pages --- app/components/dashboard/dashboard-home.tsx | 60 +- app/components/dashboard/header.tsx | 88 +- .../digital-innovation-page.tsx | 114 +- .../green-innovation-page.tsx | 41 +- .../innovation-built-inside-page.tsx | 32 +- .../mange-ideas-tech-page.tsx | 1448 +++++++++-------- .../process-innovation-page.tsx | 65 +- .../product-innovation-page.tsx | 76 +- .../project-management-page.tsx | 63 +- .../dashboard/strategic-alignment-popup.tsx | 36 +- app/components/ecosystem/info-panel.tsx | 225 +-- app/components/ecosystem/network-graph.tsx | 128 +- app/components/ui/Calendar.tsx | 2 +- app/types/util.type.ts | 4 +- 14 files changed, 1383 insertions(+), 999 deletions(-) diff --git a/app/components/dashboard/dashboard-home.tsx b/app/components/dashboard/dashboard-home.tsx index 7e367fa..900a8df 100644 --- a/app/components/dashboard/dashboard-home.tsx +++ b/app/components/dashboard/dashboard-home.tsx @@ -1,3 +1,4 @@ +import jalaali from "jalaali-js"; import { Book, CheckCircle } from "lucide-react"; import { useEffect, useState } from "react"; import toast from "react-hot-toast"; @@ -24,6 +25,7 @@ import { InteractiveBarChart } from "./interactive-bar-chart"; import { DashboardLayout } from "./layout"; export function DashboardHome() { + const { jy } = jalaali.toJalaali(new Date()); const [dashboardData, setDashboardData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -39,20 +41,23 @@ export function DashboardHome() { revenueI: number; }[] >([]); - // const [totalIncreasedCapacity, setTotalIncreasedCapacity] = - // useState(0); - useEffect(() => { - fetchDashboardData(); - }, []); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); useEffect(() => { EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) fetchDashboardData(date.start, date.end); + if (date) setDate(date); }); }, []); - const fetchDashboardData = async (startDate?: string, endDate?: string) => { + useEffect(() => { + fetchDashboardData(); + }, [date]); + + const fetchDashboardData = async () => { try { setLoading(true); setError(null); @@ -66,16 +71,16 @@ export function DashboardHome() { // Fetch top cards data const topCardsResponse = await apiService.call({ main_page_first_function: { - start_date: startDate || null, - end_date: endDate || null, + start_date: date.start || null, + end_date: date.end || null, }, }); // Fetch left section data const leftCardsResponse = await apiService.call({ main_page_second_function: { - start_date: startDate || null, - end_date: endDate || null, + start_date: date.start || null, + end_date: date.end || null, }, }); @@ -109,7 +114,10 @@ export function DashboardHome() { "sum(pre_project_income)", "sum(increased_income_after_innovation)", ], - Conditions: [["start_date", ">=", startDate || null, "and"],["start_date", "<=", endDate || null]], + Conditions: [ + ["start_date", ">=", date.start || null, "and"], + ["start_date", "<=", date.end || null], + ], GroupBy: ["related_company"], }; @@ -183,22 +191,22 @@ export function DashboardHome() { }; // RadialBarChart data for ideas visualization - const getIdeasChartData = () => { - if (!dashboardData?.topData) - return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }]; + // const getIdeasChartData = () => { + // if (!dashboardData?.topData) + // return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }]; - const registered = parseFloat( - dashboardData.topData.registered_innovation_technology_idea || "0" - ); - const ongoing = parseFloat( - dashboardData.topData.ongoing_innovation_technology_ideas || "0" - ); - const percentage = registered > 0 ? (ongoing / registered) * 100 : 0; + // const registered = parseFloat( + // dashboardData.topData.registered_innovation_technology_idea || "0" + // ); + // const ongoing = parseFloat( + // dashboardData.topData.ongoing_innovation_technology_ideas || "0" + // ); + // const percentage = registered > 0 ? (ongoing / registered) * 100 : 0; - return [ - { browser: "safari", visitors: percentage, fill: "var(--color-safari)" }, - ]; - }; + // return [ + // { browser: "safari", visitors: percentage, fill: "var(--color-safari)" }, + // ]; + // }; // const chartData = getIdeasChartData(); diff --git a/app/components/dashboard/header.tsx b/app/components/dashboard/header.tsx index 7d980b6..d8e350f 100644 --- a/app/components/dashboard/header.tsx +++ b/app/components/dashboard/header.tsx @@ -78,16 +78,22 @@ export function Header({ const { user } = useAuth(); const { jy } = jalaali.toJalaali(new Date()); + const calendarRef = useRef(null); const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false); const [isNotificationOpen, setIsNotificationOpen] = useState(false); - const [selectedDate, setSelectedDate] = useState(); const [openCalendar, setOpenCalendar] = useState(false); - const calendarRef = useRef(null); const [currentYear, setCurrentYear] = useState({ since: jy, until: jy, }); + const [selectedDate, setSelectedDate] = useState({ + sinceMonth: "بهار", + fromMonth: "زمستان", + start: `${currentYear.since}/01/01`, + end: `${currentYear.until}/12/30`, + }); + const redirectHandler = async () => { try { const getData = await apiService.post("/GenerateSsoCode"); @@ -100,56 +106,74 @@ export function Header({ const nextFromYearHandler = () => { if (currentYear && (currentYear.since ?? 0) < (currentYear.until ?? 0)) { - setCurrentYear((prev) => ({ - ...prev, - since: currentYear?.since! + 1, - })); + const data = { + ...currentYear, + since: currentYear.since! + 1, + }; + setCurrentYear(data); + EventBus.emit("dateSelected", { + ...selectedDate, + start: `${data.since}/${selectedDate.start?.split("/").slice(1).join("/")}`, + }); } }; const prevFromYearHandler = () => { - setCurrentYear((prev) => ({ - ...prev, - since: currentYear?.since! - 1, - })); + const data = { + ...currentYear, + since: currentYear.since! - 1, + }; + setCurrentYear(data); + EventBus.emit("dateSelected", { + ...selectedDate, + start: `${data.since}/${selectedDate.start?.split("/").slice(1).join("/")}`, + }); }; const selectFromDateHandler = (val: MonthItem) => { const data = { + ...selectedDate, start: `${currentYear.since}/${val.start}`, sinceMonth: val.label, }; - setSelectedDate((prev) => ({ - ...prev, - ...data, - })); + setSelectedDate(data); + EventBus.emit("dateSelected", data); }; const nextUntilYearHandler = () => { - setCurrentYear((prev) => ({ - ...prev, - until: currentYear?.until! + 1, - })); + const data = { + ...currentYear, + until: currentYear.until! + 1, + }; + setCurrentYear(data); + EventBus.emit("dateSelected", { + ...selectedDate, + end: `${data.until}/${selectedDate?.end?.split("/").slice(1).join("/")}`, + }); }; const prevUntilYearHandler = () => { if (currentYear && (currentYear.since ?? 0) < (currentYear.until ?? 0)) { - setCurrentYear((prev) => ({ - ...prev, - until: currentYear?.until! - 1, - })); + const data = { + ...currentYear, + until: currentYear.until! - 1, + }; + setCurrentYear(data); + EventBus.emit("dateSelected", { + ...selectedDate, + end: `${data.until}/${selectedDate.end?.split("/").slice(1).join("/")}`, + }); } }; const selectUntilDateHandler = (val: MonthItem) => { const data = { + ...selectedDate, end: `${currentYear.until}/${val.end}`, fromMonth: val.label, }; - setSelectedDate((prev) => ({ - ...prev, - ...data, - })); + setSelectedDate(data); + EventBus.emit("dateSelected", data); toggleCalendar(); }; @@ -172,10 +196,6 @@ export function Header({ }; }, []); - useEffect(() => { - EventBus.emit("dateSelected", selectedDate); - }, [currentYear, selectedDate]); - return (
{selectedDate ? ( -
+
از {selectedDate?.sinceMonth} @@ -245,7 +265,7 @@ export function Header({
{openCalendar && ( -
+
- + ([]); 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 [totalCount, setTotalCount] = useState(0); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); const [actualTotalCount, setActualTotalCount] = useState(0); const [statsLoading, setStatsLoading] = useState(false); const [rating, setRating] = useState([]); @@ -281,7 +288,11 @@ export function DigitalInnovationPage() { "reduce_costs_percent", ], Sorts: [[sortConfig.field, sortConfig.direction]], - Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری دیجیتال", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); @@ -294,16 +305,16 @@ export function DigitalInnovationPage() { if (reset) { setProjects(parsedData); // calculateAverage(parsedData); - setTotalCount(parsedData.length); + // setTotalCount(parsedData.length); } else { setProjects((prev) => [...prev, ...parsedData]); - setTotalCount((prev) => prev + parsedData.length); + // setTotalCount((prev) => prev + parsedData.length); } setHasMore(parsedData.length === pageSize); } else { if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } @@ -311,14 +322,14 @@ export function DigitalInnovationPage() { console.error("Error parsing project data:", parseError); if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } } else { if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } @@ -326,7 +337,7 @@ export function DigitalInnovationPage() { toast.error(response.message || "خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } @@ -335,7 +346,7 @@ export function DigitalInnovationPage() { toast.error("خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } finally { @@ -356,7 +367,15 @@ export function DigitalInnovationPage() { fetchTable(true); fetchTotalCount(); fetchStats(); - }, [sortConfig]); + }, [sortConfig, date]); + + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); useEffect(() => { if (currentPage > 1) { @@ -412,19 +431,23 @@ export function DigitalInnovationPage() { direction: prev.field === field && prev.direction === "asc" ? "desc" : "asc", })); - fetchTotalCount(); - fetchStats(); + fetchTotalCount(date?.start, date?.end); + fetchStats(date?.start, date?.end); setCurrentPage(1); setProjects([]); setHasMore(true); }; - const fetchTotalCount = async () => { + const fetchTotalCount = async (startDate?: string, endDate?: string) => { try { const response = await apiService.select({ ProcessName: "project", OutputFields: ["count(project_no)"], - Conditions: [["type_of_innovation", "=", "نوآوری دیجیتال"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری دیجیتال", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -451,7 +474,10 @@ export function DigitalInnovationPage() { try { setStatsLoading(true); const raw = await apiService.call({ - innovation_digital_function: {}, + innovation_digital_function: { + start_date: date?.start || null, + end_date: date?.end || null, + }, }); // let payload: DigitalInnovationMetrics = raw?.data; @@ -529,33 +555,33 @@ export function DigitalInnovationPage() { // 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 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; diff --git a/app/components/dashboard/project-management/green-innovation-page.tsx b/app/components/dashboard/project-management/green-innovation-page.tsx index c24e5b6..2f47bf9 100644 --- a/app/components/dashboard/project-management/green-innovation-page.tsx +++ b/app/components/dashboard/project-management/green-innovation-page.tsx @@ -26,8 +26,9 @@ import { TableHeader, TableRow, } from "~/components/ui/table"; -import { formatNumber } from "~/lib/utils"; +import { EventBus, formatNumber } from "~/lib/utils"; +import jalaali from "jalaali-js"; import { Building2, ChevronDown, @@ -46,6 +47,7 @@ import { import toast from "react-hot-toast"; import apiService from "~/lib/api"; import { formatCurrency } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import DashboardLayout from "../layout"; // moment.loadPersian({ usePersianDigits: true }); @@ -157,6 +159,7 @@ const columns = [ ]; export function GreenInnovationPage() { + const { jy } = jalaali.toJalaali(new Date()); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -166,6 +169,10 @@ export function GreenInnovationPage() { const [totalCount, setTotalCount] = useState(0); const [actualTotalCount, setActualTotalCount] = useState(0); const [statsLoading, setStatsLoading] = useState(false); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); const [stats, setStats] = useState(); const [sortConfig, setSortConfig] = useState({ field: "start_date", @@ -288,7 +295,11 @@ export function GreenInnovationPage() { "observer", ], Sorts: [[sortConfig.field, sortConfig.direction]], - Conditions: [["type_of_innovation", "=", "نوآوری سبز"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری سبز", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); if (response.state === 0) { @@ -350,6 +361,14 @@ export function GreenInnovationPage() { } }; + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + const loadMore = useCallback(() => { if (hasMore && !loading) { setCurrentPage((prev) => prev + 1); @@ -359,11 +378,11 @@ export function GreenInnovationPage() { useEffect(() => { fetchProjects(true); fetchTotalCount(); - }, [sortConfig]); + }, [sortConfig, date]); useEffect(() => { fetchStats(); - }, [selectedProjects]); + }, [selectedProjects, date]); useEffect(() => { if (currentPage > 1) { @@ -416,7 +435,11 @@ export function GreenInnovationPage() { const response = await apiService.select({ ProcessName: "project", OutputFields: ["count(project_no)"], - Conditions: [["type_of_innovation", "=", "نوآوری سبز"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری سبز", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], }); if (response.state === 0) { const dataString = response.data; @@ -448,6 +471,8 @@ export function GreenInnovationPage() { selectedProjects.size > 0 ? Array.from(selectedProjects).join(" , ") : "", + start_date: date?.start || null, + end_date: date?.end || null, }, }); let payload: any = raw?.data; @@ -686,12 +711,6 @@ export function GreenInnovationPage() { { name: recycleParams.food.label, pv: 30, amt: 50 }, ]); - // useEffect(() => { - // EventBus.on("dateSelected", (date) => { - // debugger; - // }); - // }, []); - return (
diff --git a/app/components/dashboard/project-management/innovation-built-inside-page.tsx b/app/components/dashboard/project-management/innovation-built-inside-page.tsx index 9a0702f..c9204bb 100644 --- a/app/components/dashboard/project-management/innovation-built-inside-page.tsx +++ b/app/components/dashboard/project-management/innovation-built-inside-page.tsx @@ -19,6 +19,7 @@ import { TableRow, } from "~/components/ui/table"; +import jalaali from "jalaali-js"; import { ChevronDown, ChevronUp, @@ -40,7 +41,8 @@ import { XAxis, } from "recharts"; import apiService from "~/lib/api"; -import { formatCurrency, formatNumber } from "~/lib/utils"; +import { EventBus, formatCurrency, formatNumber } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import DashboardLayout from "../layout"; interface innovationBuiltInDate { @@ -177,6 +179,7 @@ const dialogChartData = [ ]; export function InnovationBuiltInsidePage() { + const { jy } = jalaali.toJalaali(new Date()); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -191,6 +194,10 @@ export function InnovationBuiltInsidePage() { field: "start_date", direction: "asc", }); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); const [tblAvarage, setTblAvarage] = useState(0); const [selectedProjects, setSelectedProjects] = useState>(); @@ -310,7 +317,11 @@ export function InnovationBuiltInsidePage() { "technology_maturity_level", ], Sorts: [[sortConfig.field, sortConfig.direction]], - Conditions: [["type_of_innovation", "=", "نوآوری ساخت داخل"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری ساخت داخل", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); if (response.state === 0) { @@ -416,13 +427,21 @@ export function InnovationBuiltInsidePage() { } }, [hasMore, loading]); + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + useEffect(() => { fetchProjects(true); - }, [sortConfig]); + }, [sortConfig, date]); useEffect(() => { fetchStats(); - }, [selectedProjects]); + }, [selectedProjects, date]); useEffect(() => { if (currentPage > 1) { @@ -480,6 +499,8 @@ export function InnovationBuiltInsidePage() { selectedProjects && selectedProjects?.size > 0 ? Array.from(selectedProjects).join(" , ") : "", + start_date: date?.start || null, + end_date: date?.end || null, }, }); let payload: any = raw?.data; @@ -624,7 +645,8 @@ export function InnovationBuiltInsidePage() { variant="ghost" size="sm" onClick={() => handleProjectDetails(item)} - className="text-pr-green hover:text-pr-green underline-offset-4 underline font-normal hover:bg-emerald-500/20 p-2 h-auto"> + className="text-pr-green hover:text-pr-green underline-offset-4 underline font-normal hover:bg-emerald-500/20 p-2 h-auto" + > جزئیات بیشتر ); diff --git a/app/components/dashboard/project-management/mange-ideas-tech-page.tsx b/app/components/dashboard/project-management/mange-ideas-tech-page.tsx index e4be03d..0238c04 100644 --- a/app/components/dashboard/project-management/mange-ideas-tech-page.tsx +++ b/app/components/dashboard/project-management/mange-ideas-tech-page.tsx @@ -1,15 +1,40 @@ -import { ChevronDown, ChevronUp, RefreshCw, Eye, Star, TrendingUp, Hexagon, Download } from "lucide-react"; -import { useCallback, useEffect, useRef, useState, useMemo, memo } from "react"; +import jalaali from "jalaali-js"; +import { + ChevronDown, + ChevronUp, + Download, + Hexagon, + RefreshCw, + Star, + TrendingUp, +} from "lucide-react"; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import toast from "react-hot-toast"; -import { Badge } from "~/components/ui/badge"; +import { + Bar, + BarChart, + CartesianGrid, + Label, + LabelList, + PolarGrid, + PolarRadiusAxis, + RadialBar, + RadialBarChart, + ResponsiveContainer, + XAxis, + YAxis, +} from "recharts"; +import { BaseCard } from "~/components/ui/base-card"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; +import { ChartContainer, type ChartConfig } from "~/components/ui/chart"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "~/components/ui/dialog"; +import { MetricCard } from "~/components/ui/metric-card"; import { Table, TableBody, @@ -19,20 +44,9 @@ import { TableRow, } from "~/components/ui/table"; import apiService from "~/lib/api"; -import { formatCurrency, formatNumber } from "~/lib/utils"; +import { EventBus, formatCurrency, formatNumber } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; 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 "recharts" -import { MetricCard } from "~/components/ui/metric-card"; - - interface IdeaData { idea_title: string; @@ -86,9 +100,19 @@ type ColumnDef = { const columns: ColumnDef[] = [ { key: "idea_title", label: "عنوان ایده", sortable: true, width: "250px" }, - { key: "idea_registration_date", label: "تاریخ ثبت ایده", sortable: true, width: "180px" }, + { + 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: "increased_revenue", + label: "درآمد حاصل از ایده", + sortable: true, + width: "180px", + }, { key: "details", label: "جزئیات بیشتر", sortable: false, width: "120px" }, ]; @@ -100,128 +124,144 @@ const VerticalBarChart = memo<{ getChartStatusColor: (status: string) => string; toPersianDigits: (input: string | number) => string; formatNumber: (value: number) => string; -}>(({ chartData, loadingChart, chartConfig, getChartStatusColor, toPersianDigits, formatNumber }) => { - if (loadingChart) { - return ( -
- {/* Chart title skeleton */} -
+}>( + ({ + chartData, + loadingChart, + chartConfig, + getChartStatusColor, + toPersianDigits, + formatNumber, + }) => { + if (loadingChart) { + return ( +
+ {/* Chart title skeleton */} +
- {/* Chart area skeleton */} -
- {/* Y-axis labels */} -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
- - {/* Bars skeleton */} -
- {Array.from({ length: 4 }).map((_, i) => ( -
- {/* Bar */} + {/* Chart area skeleton */} +
+ {/* Y-axis labels */} +
+ {Array.from({ length: 4 }).map((_, i) => (
- {/* X-axis label */} -
-
- ))} + ))} +
+ + {/* Bars skeleton */} +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ {/* Bar */} +
+ {/* X-axis label */} +
+
+ ))} +
-
- ); - } + ); + } + + if (!chartData.length) { + return ( +
+

+ وضعیت ایده ها +

+

هیچ داده‌ای یافت نشد

+
+ ); + } + + // Prepare data for recharts + const rechartData = useMemo( + () => + chartData.map((item) => ({ + status: item.idea_status, + count: item.idea_status_count, + fill: getChartStatusColor(item.idea_status), + })), + [chartData, getChartStatusColor] + ); - if (!chartData.length) { return ( -
-

وضعیت ایده ها

-

هیچ داده‌ای یافت نشد

-
+ + + + + + toPersianDigits(value)} + label={{ + value: "تعداد برنامه ها", + angle: -90, + position: "insideLeft", + fill: "#94a3b8", + fontSize: 11, + offset: 0, + dy: 0, + style: { textAnchor: "middle" }, + }} + /> + + `${formatNumber(Math.round(v))}`} + /> + + + + ); } - - // Prepare data for recharts - const rechartData = useMemo(() => chartData.map((item) => ({ - status: item.idea_status, - count: item.idea_status_count, - fill: getChartStatusColor(item.idea_status), - })), [chartData, getChartStatusColor]); - - return ( - - - - - - toPersianDigits(value)} - label={{ - value: "تعداد برنامه ها", - angle: -90, - position: "insideLeft", - fill: "#94a3b8", - fontSize: 11, - offset: 0, - dy: 0, - style: { textAnchor: "middle" }, - }} - /> - - `${formatNumber(Math.round(v))}`} - /> - - - - - ); -}); +); const MemoizedVerticalBarChart = VerticalBarChart; export function ManageIdeasTechPage() { + const { jy } = jalaali.toJalaali(new Date()); const [ideas, setIdeas] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -236,6 +276,10 @@ export function ManageIdeasTechPage() { field: "idea_title", direction: "asc", }); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); // People ranking state const [peopleRanking, setPeopleRanking] = useState([]); @@ -293,7 +337,10 @@ export function ManageIdeasTechPage() { ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, Sorts: [[sortConfig.field, sortConfig.direction]], - Conditions: [], + Conditions: [ + ["idea_registration_date", ">=", date?.start || null, "and"], + ["idea_registration_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -361,13 +408,21 @@ export function ManageIdeasTechPage() { } }, [hasMore, loading, loadingMore]); + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + useEffect(() => { fetchIdeas(true); fetchTotalCount(); fetchPeopleRanking(); fetchChartData(); fetchStatsData(); - }, [sortConfig]); + }, [sortConfig, date]); useEffect(() => { if (currentPage > 1) { @@ -380,7 +435,8 @@ export function ManageIdeasTechPage() { const scrollContainer = scrollContainerRef.current; const handleScroll = () => { - if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return; + if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) + return; if (scrollTimeoutRef.current) { clearTimeout(scrollTimeoutRef.current); @@ -397,7 +453,9 @@ export function ManageIdeasTechPage() { }; if (scrollContainer) { - scrollContainer.addEventListener("scroll", handleScroll, { passive: true }); + scrollContainer.addEventListener("scroll", handleScroll, { + passive: true, + }); } return () => { @@ -427,7 +485,10 @@ export function ManageIdeasTechPage() { const response = await apiService.select({ ProcessName: "idea", OutputFields: ["count(idea_title)"], - Conditions: [], + Conditions: [ + ["idea_registration_date", ">=", date?.start || null, "and"], + ["idea_registration_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -456,6 +517,10 @@ export function ManageIdeasTechPage() { ProcessName: "idea", OutputFields: ["full_name", "count(full_name)"], GroupBy: ["full_name"], + Conditions: [ + ["idea_registration_date", ">=", date?.start || null, "and"], + ["idea_registration_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -465,12 +530,14 @@ export function ManageIdeasTechPage() { const parsedData = JSON.parse(dataString); if (Array.isArray(parsedData)) { // Calculate rankings and stars - const counts = parsedData.map(item => item.full_name_count); + 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 sortedData = parsedData.sort( + (a, b) => b.full_name_count - a.full_name_count + ); const rankedPeople = []; let currentRank = 1; @@ -480,13 +547,17 @@ export function ManageIdeasTechPage() { 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 + 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 normalizedScore = + maxCount === minCount + ? 1 + : (item.full_name_count - minCount) / (maxCount - minCount); const stars = Math.max(1, Math.round(normalizedScore * 5)); rankedPeople.push({ @@ -503,7 +574,9 @@ export function ManageIdeasTechPage() { } } } else { - toast.error(response.message || "خطا در دریافت اطلاعات رتبه‌بندی افراد"); + toast.error( + response.message || "خطا در دریافت اطلاعات رتبه‌بندی افراد" + ); } } catch (error) { console.error("Error fetching people ranking:", error); @@ -521,6 +594,10 @@ export function ManageIdeasTechPage() { ProcessName: "idea", OutputFields: ["idea_status", "count(idea_status)"], GroupBy: ["idea_status"], + Conditions: [ + ["idea_registration_date", ">=", date?.start || null, "and"], + ["idea_registration_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -551,7 +628,10 @@ export function ManageIdeasTechPage() { setLoadingStats(true); const response = await apiService.call({ - idea_page_function: {} + idea_page_function: { + start_date: date?.start || null, + end_date: date?.end || null, + }, }); if (response.state === 0) { @@ -621,11 +701,14 @@ export function ManageIdeasTechPage() { }; // Chart configuration for shadcn/ui - const chartConfig: ChartConfig = useMemo(() => ({ - count: { - label: "تعداد", - }, - }), []); + const chartConfig: ChartConfig = useMemo( + () => ({ + count: { + label: "تعداد", + }, + }), + [] + ); // Color palette for idea status // Specific colors for idea statuses @@ -644,7 +727,16 @@ export function ManageIdeasTechPage() { } }, []); - const statusColorPalette = ["#3AEA83", "#69C8EA", "#F76276", "#FFD700", "#A757FF", "#E884CE", "#C3BF8B", "#FB7185"]; + const statusColorPalette = [ + "#3AEA83", + "#69C8EA", + "#F76276", + "#FFD700", + "#A757FF", + "#E884CE", + "#C3BF8B", + "#FB7185", + ]; // Build a mapping of status value -> color based on loaded ideas const statusColorMap = useMemo(() => { @@ -681,9 +773,7 @@ export function ManageIdeasTechPage() { switch (column.key) { case "idea_title": - return ( - {String(value)} - ); + return {String(value)}; case "idea_registration_date": return ( @@ -708,19 +798,20 @@ export function ManageIdeasTechPage() { case "increased_revenue": return ( - {formatCurrency(String(value || "0")).replace("ریال" , "")} + {formatCurrency(String(value || "0")).replace("ریال", "")} ); case "details": return ( ); + variant="ghost" + size="sm" + onClick={() => handleShowDetails(item)} + className="underline text-pr-green underline-offset-4 text-sm" + > + جزئیات بیشتر + + ); default: return ( @@ -730,18 +821,15 @@ export function ManageIdeasTechPage() { } }; - - return (
-
{/* People Ranking Table */}

رتبه بندی نوآوران -

+
@@ -762,7 +850,10 @@ export function ManageIdeasTechPage() { {loadingPeople ? ( Array.from({ length: 10 }).map((_, index) => ( - +
@@ -771,9 +862,14 @@ export function ManageIdeasTechPage() {
- {Array.from({ length: 5 }).map((_, starIndex) => ( -
- ))} + {Array.from({ length: 5 }).map( + (_, starIndex) => ( +
+ ) + )}
@@ -788,7 +884,10 @@ export function ManageIdeasTechPage() { ) : ( peopleRanking.map((person) => ( - +
{toPersianDigits(person.ranking)} @@ -801,16 +900,18 @@ export function ManageIdeasTechPage() {
- {Array.from({ length: 5 }).map((_, starIndex) => ( - - ))} + {Array.from({ length: 5 }).map( + (_, starIndex) => ( + + ) + )}
@@ -832,319 +933,326 @@ export function ManageIdeasTechPage() { {/* Main Ideas Table */}

- لیست ایده ها -

- - -
- - - - {columns.map((column) => ( - - {column.sortable ? ( - - ) : ( - column.label - )} - - ))} - - - - {loading ? ( - Array.from({ length: 20 }).map((_, index) => ( - - {columns.map((column) => ( - -
-
-
-
- - ))} - - )) - ) : ideas.length === 0 ? ( - - - - هیچ ایده‌ای یافت نشد - - - - ) : ( - ideas.map((idea, index) => ( - - {columns.map((column) => ( - - {renderCellContent(idea, column)} - - ))} - - )) - )} - -
-
+ لیست ایده ها + + + +
+ + + + {columns.map((column) => ( + + {column.sortable ? ( + + ) : ( + column.label + )} + + ))} + + + + {loading ? ( + Array.from({ length: 20 }).map((_, index) => ( + + {columns.map((column) => ( + +
+
+
+
+ + ))} + + )) + ) : ideas.length === 0 ? ( + + + + هیچ ایده‌ای یافت نشد + + + + ) : ( + ideas.map((idea, index) => ( + + {columns.map((column) => ( + + {renderCellContent(idea, column)} + + ))} + + )) + )} + +
+
- {/* Infinite scroll trigger */} -
+ {/* Infinite scroll trigger */} +
+ -
-
- - {/* Footer */} -
-
- کل ایده‌ها: {toPersianDigits(actualTotalCount)} -
-
-
- {loadingMore && ( -
-
- - - -
-
- )} -
- {/* Chart Section */} - - - -
- - {loadingStats ? ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ) : ( -
- - 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} - > - - - - - - -
-
- -
ثبت شده :
- {formatNumber( - statsData - ?.registered_innovation_technology_idea || "0", - )} -
- -
در حال اجرا :
- {formatNumber( - statsData - ?.ongoing_innovation_technology_ideas || "0", - )} -
-
-
-
- )} -
- - {loadingStats ? ( -
-
-
-
-
-
-
-
-
-
-
-
-
- ) : ( - - )} + {/* Footer */} +
+
+ کل ایده‌ها: {toPersianDigits(actualTotalCount)} +
+
+ + {loadingMore && ( +
+
+ + +
+
+ )} +
+ {/* Chart Section */} + + + +
+ + {loadingStats ? ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : ( +
+ + 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} + > + + + + + + +
+
+ +
ثبت شده :
+ {formatNumber( + statsData?.registered_innovation_technology_idea || + "0" + )} +
+ +
در حال اجرا :
+ {formatNumber( + statsData?.ongoing_innovation_technology_ideas || "0" + )} +
+
+
+
+ )} +
+ + {loadingStats ? ( +
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : ( + + )} +
{/* Details Dialog */} @@ -1156,192 +1264,226 @@ export function ManageIdeasTechPage() { - {selectedIdea &&
-
- {/* مشخصات ایده پردازان Section */} -
-

- مشخصات ایده پردازان -

-
-
-
- - نام ایده پرداز: + {selectedIdea && ( +
+
+ {/* مشخصات ایده پردازان Section */} +
+

+ مشخصات ایده پردازان +

+
+
+
+ + + نام ایده پرداز: + +
+ + {selectedIdea.full_name || "-"} + +
+
+
+ + + شماره پرسنلی: + +
+ + {toPersianDigits(selectedIdea.personnel_number) || + "۱۳۰۶۵۸۰۶"} + +
+
+
+ + + مدیریت: + +
+ + {selectedIdea.management || "مدیریت توسعه"} + +
+
+
+ + + معاونت: + +
+ + {selectedIdea.deputy || "توسعه"} + +
+
+
+ + + اعضای تیم: + +
+ + {selectedIdea.innovator_team_members || + "رضا حسین پور, محمد رضا شیاطی, محمد مددی"} +
- {selectedIdea.full_name || "-"}
-
-
- - شماره پرسنلی: +
+ + {/* مشخصات ایده Section */} +
+

+ مشخصات ایده +

+
+
+
+ + + تاریخ ثبت ایده: + +
+ + {formatDate(selectedIdea.idea_registration_date) || + "-"} + +
+
+
+ + + نوع نوآوری: + +
+ + {selectedIdea.innovation_type || "-"} + +
+
+
+ + + اصالت ایده: + +
+ + {selectedIdea.idea_originality || "-"} + +
+
+
+ + + محور ایده: + +
+ + {selectedIdea.idea_axis || "-"} +
- {toPersianDigits(selectedIdea.personnel_number) || "۱۳۰۶۵۸۰۶"}
-
-
- - مدیریت: +
+ {/* نتایج و خروجی ها Section */} +
+

+ نتایج و خروجی ها +

+
+
+
+ + + درآمد حاصل: + +
+ + {formatNumber(selectedIdea.increased_revenue) || "-"} + + میلیون ریال + +
- {selectedIdea.management || "مدیریت توسعه"} -
-
-
- - معاونت: +
+
+ + + مقاله چاپ شده: + +
+ + + دانلود +
- {selectedIdea.deputy || "توسعه"} -
-
-
- - اعضای تیم: +
+
+ + + پتنت ثبت شده: + +
+ + + دانلود +
- - {selectedIdea.innovator_team_members || "رضا حسین پور, محمد رضا شیاطی, محمد مددی"} -
- - {/* مشخصات ایده Section */} -
-

- مشخصات ایده -

-
-
-
- - تاریخ ثبت ایده: -
- {formatDate(selectedIdea.idea_registration_date) || "-"} -
-
-
- - نوع نوآوری: -
- {selectedIdea.innovation_type || "-"} -
-
-
- - اصالت ایده: -
- {selectedIdea.idea_originality || "-"} -
-
-
- - محور ایده: -
- {selectedIdea.idea_axis || "-"} +
+ {/* شرح ایده Section */} +
+

+ شرح ایده +

+
+

+ {selectedIdea.idea_description || "-"} +

-
- {/* نتایج و خروجی ها Section */} -
-

- نتایج و خروجی ها -

-
-
-
- - درآمد حاصل: -
- {formatNumber(selectedIdea.increased_revenue) || "-"} - - میلیون ریال - - + {/* شرح وضعیت موجود ایده Section */} +
+

+ شرح وضعیت موجود ایده +

+
+

+ {selectedIdea.idea_current_status_description || "-"} +

-
-
- - مقاله چاپ شده: -
- - +
- دانلود - + {/* منافع حاصل از ایده Section */} +
+

+ منافع حاصل از ایده +

+
+

+ {selectedIdea.idea_execution_benefits || "-"} +

-
-
- - پتنت ثبت شده: -
- - - دانلود - +
+ + {/* بهبود های فرآیندی ایده Section */} +
+

+ بهبود های فرآیندی ایده +

+
+

+ {selectedIdea.process_improvements || "-"} +

-
- - {/* شرح ایده Section */} -
-

- شرح ایده -

-
-

- {selectedIdea.idea_description || - "-" - } -

-
-
- - {/* شرح وضعیت موجود ایده Section */} -
-

- شرح وضعیت موجود ایده -

-
-

- {selectedIdea.idea_current_status_description || - "-" - } -

-
-
- - {/* منافع حاصل از ایده Section */} -
-

- منافع حاصل از ایده -

-
-

- {selectedIdea.idea_execution_benefits || - "-" - } -

-
-
- - {/* بهبود های فرآیندی ایده Section */} -
-

- بهبود های فرآیندی ایده -

-
-

- {selectedIdea.process_improvements || - "-" - } -

-
-
- -
-
} + )}
diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx index cf1a3e7..1a387cd 100644 --- a/app/components/dashboard/project-management/process-innovation-page.tsx +++ b/app/components/dashboard/project-management/process-innovation-page.tsx @@ -1,3 +1,4 @@ +import jalaali from "jalaali-js"; import { Building2, ChevronDown, @@ -35,7 +36,8 @@ import { TableRow, } from "~/components/ui/table"; import apiService from "~/lib/api"; -import { formatNumber } from "~/lib/utils"; +import { EventBus, formatNumber } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import { DashboardLayout } from "../layout"; moment.loadPersian({ usePersianDigits: true }); @@ -117,13 +119,18 @@ const columns = [ ]; export function ProcessInnovationPage() { + const { jy } = jalaali.toJalaali(new Date()); const [projects, setProjects] = useState([]); 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 [totalCount, setTotalCount] = useState(0); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); const [actualTotalCount, setActualTotalCount] = useState(0); const [statsLoading, setStatsLoading] = useState(false); const [stats, setStats] = useState({ @@ -196,13 +203,13 @@ export function ProcessInnovationPage() { const fetchingRef = useRef(false); // Selection handlers - const handleSelectAll = () => { - if (selectedProjects.size === projects.length) { - setSelectedProjects(new Set()); - } else { - setSelectedProjects(new Set(projects.map((p) => p.project_no))); - } - }; + // const handleSelectAll = () => { + // if (selectedProjects.size === projects.length) { + // setSelectedProjects(new Set()); + // } else { + // setSelectedProjects(new Set(projects.map((p) => p.project_no))); + // } + // }; const handleSelectProject = (projectNo: string) => { const newSelected = new Set(selectedProjects); @@ -256,7 +263,11 @@ export function ProcessInnovationPage() { "observer", ], Sorts: [["start_date", "asc"]], - Conditions: [["type_of_innovation", "=", "نوآوری در فرآیند"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری در فرآیند", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); @@ -268,16 +279,16 @@ export function ProcessInnovationPage() { if (Array.isArray(parsedData)) { if (reset) { setProjects(parsedData); - setTotalCount(parsedData.length); + // setTotalCount(parsedData.length); } else { setProjects((prev) => [...prev, ...parsedData]); - setTotalCount((prev) => prev + parsedData.length); + // setTotalCount((prev) => prev + parsedData.length); } setHasMore(parsedData.length === pageSize); } else { if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } @@ -285,14 +296,14 @@ export function ProcessInnovationPage() { console.error("Error parsing project data:", parseError); if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } } else { if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } @@ -300,7 +311,7 @@ export function ProcessInnovationPage() { toast.error(response.message || "خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } @@ -309,7 +320,7 @@ export function ProcessInnovationPage() { toast.error("خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); - setTotalCount(0); + // setTotalCount(0); } setHasMore(false); } finally { @@ -325,14 +336,22 @@ export function ProcessInnovationPage() { } }, [hasMore, loading]); + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + useEffect(() => { fetchProjects(true); fetchTotalCount(); - }, [sortConfig]); + }, [sortConfig, date]); useEffect(() => { fetchStats(); - }, [selectedProjects]); + }, [selectedProjects, date]); useEffect(() => { if (currentPage > 1) { @@ -382,7 +401,11 @@ export function ProcessInnovationPage() { const response = await apiService.select({ ProcessName: "project", OutputFields: ["count(project_no)"], - Conditions: [["type_of_innovation", "=", "نوآوری در فرآیند"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری در فرآیند", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -416,6 +439,8 @@ export function ProcessInnovationPage() { selectedProjects.size > 0 ? Array.from(selectedProjects).join(" , ") : "", + start_date: date?.start || null, + end_date: date?.end || null, }, }); diff --git a/app/components/dashboard/project-management/product-innovation-page.tsx b/app/components/dashboard/project-management/product-innovation-page.tsx index ed22582..0dd624d 100644 --- a/app/components/dashboard/project-management/product-innovation-page.tsx +++ b/app/components/dashboard/project-management/product-innovation-page.tsx @@ -14,6 +14,7 @@ import { PopoverTrigger, } from "~/components/ui/popover"; +import jalaali from "jalaali-js"; import { CartesianGrid, Legend, @@ -42,7 +43,8 @@ import { } from "~/components/ui/table"; import { Tooltip as TooltipSh, TooltipTrigger } from "~/components/ui/tooltip"; import apiService from "~/lib/api"; -import { formatNumber, handleDataValue } from "~/lib/utils"; +import { EventBus, formatNumber, handleDataValue } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import { DashboardLayout } from "../layout"; interface ProjectData { @@ -196,7 +198,8 @@ export default function Timeline(valueTimeLine: string) { } export function ProductInnovationPage() { - const [showPopup, setShowPopup] = useState(false); + // const [showPopup, setShowPopup] = useState(false); + const { jy } = jalaali.toJalaali(new Date()); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -261,17 +264,24 @@ export function ProductInnovationPage() { }, }); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); const observerRef = useRef(null); const fetchingRef = useRef(false); const handleProjectDetails = async (project: ProductInnovationData) => { setSelectedProjectDetails(project); - console.log(project); setDetailsDialogOpen(true); - await fetchPopupData(project); + await fetchPopupData(project, date?.start, date?.end); }; - const fetchPopupData = async (project: ProductInnovationData) => { + const fetchPopupData = async ( + project: ProductInnovationData, + startDate?: string, + endDate?: string + ) => { try { setPopupLoading(true); @@ -279,6 +289,8 @@ export function ProductInnovationPage() { const statsResponse = await apiService.call({ innovation_product_popup_function1: { project_id: project.project_id, + start_date: startDate || null, + end_date: endDate || null, }, }); @@ -361,7 +373,11 @@ export function ProductInnovationPage() { "issuing_authority", ], Sorts: [["start_date", "asc"]], - Conditions: [["type_of_innovation", "=", "نوآوری در محصول"]], + Conditions: [ + ["type_of_innovation", "=", "نوآوری در محصول", "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); @@ -424,14 +440,14 @@ export function ProductInnovationPage() { } }; - const fetchStats = async (startDate?: string, endDate?: string) => { + const fetchStats = async () => { try { setStatsLoading(true); const raw = await apiService.call({ innovation_product_function: { - start_date: startDate, - end_date: endDate, + start_date: date?.start || null, + end_date: date?.end || null, }, }); @@ -495,13 +511,21 @@ export function ProductInnovationPage() { } }; + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + useEffect(() => { fetchProjects(true); - }, [sortConfig]); + }, [sortConfig, date]); useEffect(() => { fetchStats(); - }, []); + }, [date]); useEffect(() => { if (currentPage > 1) { @@ -546,15 +570,15 @@ export function ProductInnovationPage() { setHasMore(true); }; - const formatCurrency = (amount: string | number) => { - if (!amount) return "0 ریال"; - const numericAmount = - typeof amount === "string" - ? parseFloat(amount.replace(/,/g, "")) - : amount; - if (isNaN(numericAmount)) return "0 ریال"; - return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال"; - }; + // const formatCurrency = (amount: string | number) => { + // if (!amount) return "0 ریال"; + // const numericAmount = + // typeof amount === "string" + // ? parseFloat(amount.replace(/,/g, "")) + // : amount; + // if (isNaN(numericAmount)) return "0 ریال"; + // return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال"; + // }; // Transform data for line chart const transformDataForLineChart = (data: any[]) => { @@ -576,12 +600,12 @@ export function ProductInnovationPage() { }); }; - const getRatingColor = (rating: string | number) => { - const numRating = typeof rating === "string" ? parseInt(rating) : rating; - if (numRating >= 150) return "text-emerald-400"; - if (numRating >= 100) return "text-blue-400"; - return "text-red-400"; - }; + // const getRatingColor = (rating: string | number) => { + // const numRating = typeof rating === "string" ? parseInt(rating) : rating; + // if (numRating >= 150) return "text-emerald-400"; + // if (numRating >= 100) return "text-blue-400"; + // return "text-red-400"; + // }; const statusColor = (status: projectStatus): any => { let el = null; diff --git a/app/components/dashboard/project-management/project-management-page.tsx b/app/components/dashboard/project-management/project-management-page.tsx index 1ff24d3..e8685fc 100644 --- a/app/components/dashboard/project-management/project-management-page.tsx +++ b/app/components/dashboard/project-management/project-management-page.tsx @@ -1,5 +1,6 @@ +import jalaali from "jalaali-js"; import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react"; -import { useCallback, useEffect, useRef, useState, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import toast from "react-hot-toast"; import { Badge } from "~/components/ui/badge"; import { Card, CardContent } from "~/components/ui/card"; @@ -13,8 +14,8 @@ import { TableRow, } from "~/components/ui/table"; import apiService from "~/lib/api"; -import { formatCurrency } from "~/lib/utils"; -import { formatNumber } from "~/lib/utils"; +import { EventBus, formatCurrency, formatNumber } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import { DashboardLayout } from "../layout"; interface ProjectData { @@ -153,6 +154,7 @@ const columns: ColumnDef[] = [ ]; export function ProjectManagementPage() { + const { jy } = jalaali.toJalaali(new Date()); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -169,6 +171,10 @@ export function ProjectManagementPage() { const fetchingRef = useRef(false); const scrollTimeoutRef = useRef(null); const scrollContainerRef = useRef(null); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); const fetchProjects = async (reset = false) => { // Prevent concurrent API calls @@ -200,7 +206,10 @@ export function ProjectManagementPage() { OutputFields: outputFields, Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, Sorts: sortField ? [[sortField, sortConfig.direction]] : [], - Conditions: [], + Conditions: [ + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -265,6 +274,13 @@ export function ProjectManagementPage() { } }; + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); const loadMore = useCallback(() => { if (hasMore && !loading && !loadingMore && !fetchingRef.current) { setCurrentPage((prev) => prev + 1); @@ -274,7 +290,7 @@ export function ProjectManagementPage() { useEffect(() => { fetchProjects(true); fetchTotalCount(); - }, [sortConfig]); + }, [sortConfig, date]); useEffect(() => { if (currentPage > 1) { @@ -287,7 +303,8 @@ export function ProjectManagementPage() { const scrollContainer = scrollContainerRef.current; const handleScroll = () => { - if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return; + if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) + return; // Clear previous timeout if (scrollTimeoutRef.current) { @@ -307,7 +324,9 @@ export function ProjectManagementPage() { }; if (scrollContainer) { - scrollContainer.addEventListener("scroll", handleScroll, { passive: true }); + scrollContainer.addEventListener("scroll", handleScroll, { + passive: true, + }); } return () => { @@ -337,7 +356,10 @@ export function ProjectManagementPage() { const response = await apiService.select({ ProcessName: "project", OutputFields: ["count(project_no)"], - Conditions: [], + Conditions: [ + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], }); if (response.state === 0) { @@ -358,14 +380,14 @@ export function ProjectManagementPage() { } }; - const handleRefresh = () => { - fetchingRef.current = false; // Reset fetching state on refresh - setCurrentPage(1); - setProjects([]); - setHasMore(true); - fetchProjects(true); - fetchTotalCount(); - }; + // const handleRefresh = () => { + // fetchingRef.current = false; // Reset fetching state on refresh + // setCurrentPage(1); + // setProjects([]); + // setHasMore(true); + // fetchProjects(true); + // fetchTotalCount(); + // }; // ...existing code... @@ -630,7 +652,7 @@ export function ProjectManagementPage() { .filter((v) => v !== null) as number[]; res["remaining_time"] = remainingValues.length ? Math.round( - remainingValues.reduce((a, b) => a + b, 0) / remainingValues.length, + remainingValues.reduce((a, b) => a + b, 0) / remainingValues.length ) : null; @@ -644,7 +666,7 @@ export function ProjectManagementPage() { const num = Number( String(raw) .toString() - .replace(/[^0-9.-]/g, ""), + .replace(/[^0-9.-]/g, "") ); return Number.isFinite(num) ? num : NaN; }) @@ -770,7 +792,10 @@ export function ProjectManagementPage() {
-
+
diff --git a/app/components/dashboard/strategic-alignment-popup.tsx b/app/components/dashboard/strategic-alignment-popup.tsx index d430dc3..9b73d84 100644 --- a/app/components/dashboard/strategic-alignment-popup.tsx +++ b/app/components/dashboard/strategic-alignment-popup.tsx @@ -1,3 +1,4 @@ +import jalaali from "jalaali-js"; import { useEffect, useReducer, useRef, useState } from "react"; import { Bar, @@ -12,7 +13,8 @@ import { import { Dialog, DialogContent, DialogHeader } from "~/components/ui/dialog"; import { Skeleton } from "~/components/ui/skeleton"; import apiService from "~/lib/api"; -import { formatNumber } from "~/lib/utils"; +import { EventBus, formatNumber } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import { ChartContainer } from "../ui/chart"; import { DropdownMenu, @@ -116,6 +118,7 @@ export function StrategicAlignmentPopup({ open, onOpenChange, }: StrategicAlignmentPopupProps) { + const { jy } = jalaali.toJalaali(new Date()); const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const contentRef = useRef(null); @@ -125,22 +128,35 @@ export function StrategicAlignmentPopup({ dropDownItems: [], }); + const [date, setDate] = useState({ + start: `${jy}/01/01`, + end: `${jy}/12/30`, + }); useEffect(() => { if (open) { fetchData(); } }, [open]); + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + const fetchData = async () => { setLoading(true); try { const response = await apiService.select({ ProcessName: "project", - OutputFields: [ - "strategic_theme", - "count(operational_fee)", - ], + OutputFields: ["strategic_theme", "count(operational_fee)"], GroupBy: ["strategic_theme"], + Conditions: [ + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], }); const responseData = @@ -170,7 +186,11 @@ export function StrategicAlignmentPopup({ "value_technology_and_innovation", "count(operational_fee)", ], - Conditions: [["strategic_theme", "=", item]], + Conditions: [ + ["strategic_theme", "=", item, "and"], + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], GroupBy: ["value_technology_and_innovation"], }); @@ -247,7 +267,9 @@ export function StrategicAlignmentPopup({ (item: StrategicAlignmentData) => ({ ...item, percentage: - total > 0 ? Math.round((item.operational_fee_count / total) * 100) : 0, + total > 0 + ? Math.round((item.operational_fee_count / total) * 100) + : 0, }) ); setData(dataWithPercentage || []); diff --git a/app/components/ecosystem/info-panel.tsx b/app/components/ecosystem/info-panel.tsx index c8fcf8a..f1682ad 100644 --- a/app/components/ecosystem/info-panel.tsx +++ b/app/components/ecosystem/info-panel.tsx @@ -1,7 +1,6 @@ "use client"; -import React, { useEffect, useState } from "react"; -import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; +import { useEffect, useState } from "react"; import { Area, AreaChart, @@ -11,9 +10,11 @@ import { XAxis, YAxis, } from "recharts"; +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { CustomBarChart } from "~/components/ui/custom-bar-chart"; import apiService from "~/lib/api"; -import { formatNumber } from "~/lib/utils"; +import { EventBus, formatNumber } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; export interface CompanyDetails { id: string; @@ -62,37 +63,52 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { const [counts, setCounts] = useState(null); const [processData, setProcessData] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [date, setDate] = useState(); + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); useEffect(() => { - const fetchCounts = async () => { - setIsLoading(true); - try { - const [countsRes, processRes] = await Promise.all([ - apiService.call({ - ecosystem_count_function: {}, - }), - apiService.call({ - process_creating_actors_function: {}, - }), - ]); - - setCounts( - JSON.parse(JSON.parse(countsRes.data).ecosystem_count_function)[0], - ); - - // Process the years data and fill missing years - const processedData = processYearsData( - JSON.parse(JSON.parse(processRes?.data)?.process_creating_actors), - ); - setProcessData(processedData); - } catch (err) { - console.error("Failed to fetch data:", err); - } finally { - setIsLoading(false); - } - }; fetchCounts(); - }, []); + }, [date]); + + const fetchCounts = async () => { + setIsLoading(true); + try { + const [countsRes, processRes] = await Promise.all([ + apiService.call({ + ecosystem_count_function: { + start_date: date?.start || null, + end_date: date?.end || null, + }, + }), + apiService.call({ + process_creating_actors_function: { + start_date: date?.start || null, + end_date: date?.end || null, + }, + }), + ]); + + setCounts( + JSON.parse(JSON.parse(countsRes.data).ecosystem_count_function)[0] + ); + + // Process the years data and fill missing years + const processedData = processYearsData( + JSON.parse(JSON.parse(processRes?.data)?.process_creating_actors) + ); + setProcessData(processedData); + } catch (err) { + console.error("Failed to fetch data:", err); + } finally { + setIsLoading(false); + } + }; // Helper function to safely parse numbers const parseNumber = (value: string | undefined): number => { @@ -103,7 +119,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { // Helper function to process years data and fill missing years const processYearsData = ( - data: ProcessActorsResponse[], + data: ProcessActorsResponse[] ): ProcessActorsData[] => { if (!data || data.length === 0) return []; @@ -121,7 +137,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { acc[item.start_year] = item.total_count; return acc; }, - {} as Record, + {} as Record ); for (let year = minYear; year <= maxYear; year++) { @@ -408,8 +424,8 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { - تعداد تفاهم نامه ها - + تعداد تفاهم نامه ها + {formatNumber(counts.mou_count)} @@ -432,7 +448,7 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
({ label: item.label, value: item.value, @@ -455,70 +471,82 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
{processData.length > 0 ? ( - - - - - - - - + + + + + + + + - - - formatNumber(value)} - /> - } /> - - {/* ✅ Use gradient for fill */} - ( - - {/* Small circle */} - - {/* Year label above point */} - - {formatPersianYear(payload.year)} - - - )} - /> - - + + + formatNumber(value)} + /> + } /> + {/* ✅ Use gradient for fill */} + ( + + {/* Small circle */} + + {/* Year label above point */} + + {formatPersianYear(payload.year)} + + + )} + /> + + ) : (
داده‌ای برای نمایش وجود ندارد @@ -526,7 +554,6 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { )}
-
); diff --git a/app/components/ecosystem/network-graph.tsx b/app/components/ecosystem/network-graph.tsx index 6cf0d9e..897d2f4 100644 --- a/app/components/ecosystem/network-graph.tsx +++ b/app/components/ecosystem/network-graph.tsx @@ -1,7 +1,9 @@ -import React, { useEffect, useRef, useState, useCallback } from "react"; import * as d3 from "d3"; -import apiService from "../../lib/api"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { EventBus } from "~/lib/utils"; +import type { CalendarDate } from "~/types/util.type"; import { useAuth } from "../../contexts/auth-context"; +import apiService from "../../lib/api"; const API_BASE_URL = import.meta.env.VITE_API_URL || "https://inogen-back.pelekan.org/api"; @@ -59,7 +61,10 @@ function isBrowser(): boolean { return typeof window !== "undefined"; } -export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps) { +export function NetworkGraph({ + onNodeClick, + onLoadingChange, +}: NetworkGraphProps) { const svgRef = useRef(null); const [nodes, setNodes] = useState([]); const [links, setLinks] = useState([]); @@ -68,6 +73,15 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps const [error, setError] = useState(null); const { token } = useAuth(); + const [date, setDate] = useState(); + useEffect(() => { + EventBus.on("dateSelected", (date: CalendarDate) => { + if (date) { + setDate(date); + } + }); + }, []); + useEffect(() => { if (isBrowser()) { const timer = setTimeout(() => setIsMounted(true), 100); @@ -80,16 +94,21 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps if (!token?.accessToken) return null; return `${API_BASE_URL}/getimage?stageID=${stageid}&nameOrID=image&token=${token.accessToken}`; }, - [token?.accessToken], + [token?.accessToken] ); - const callAPI = useCallback(async (stage_id: number) => { - return await apiService.call({ - get_values_workflow_function: { - stage_id: stage_id, - }, - }); - }, []); + const callAPI = useCallback( + async (stage_id: number) => { + return await apiService.call({ + get_values_workflow_function: { + stage_id: stage_id, + start_date: date?.start || null, + end_date: date?.end || null, + }, + }); + }, + [date] + ); useEffect(() => { if (!isMounted) return; @@ -108,7 +127,7 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps const data = parseApiResponse(JSON.parse(res.data)?.graph_production); console.log( "All available fields in first item:", - Object.keys(data[0] || {}), + Object.keys(data[0] || {}) ); // نود مرکزی @@ -121,7 +140,9 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps }; // دسته‌بندی‌ها - const categories = Array.from(new Set(data.map((item: any) => item.category))); + const categories = Array.from( + new Set(data.map((item: any) => item.category)) + ); const categoryNodes: Node[] = categories.map((cat, index) => ({ id: `cat-${index}`, @@ -170,7 +191,8 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps }, [isMounted, token, getImageUrl]); useEffect(() => { - if (!isMounted || !svgRef.current || isLoading || nodes.length === 0) return; + if (!isMounted || !svgRef.current || isLoading || nodes.length === 0) + return; const svg = d3.select(svgRef.current); const width = svgRef.current.clientWidth; @@ -225,12 +247,18 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps .forceLink(links) .id((d) => d.id) .distance(150) - .strength(0.2), + .strength(0.2) ) .force("charge", d3.forceManyBody().strength(-300)) .force("center", d3.forceCenter(width / 2, height / 2)) - .force("radial", d3.forceRadial(d => d.isCenter ? 0 : 300, width/2, height/2)) - .force("collision", d3.forceCollide().radius((d) => (d.isCenter ? 50 : 35))); + .force( + "radial", + d3.forceRadial((d) => (d.isCenter ? 0 : 300), width / 2, height / 2) + ) + .force( + "collision", + d3.forceCollide().radius((d) => (d.isCenter ? 50 : 35)) + ); // Initial zoom to show entire graph const initialScale = 0.6; @@ -242,23 +270,23 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps zoom.transform, d3.zoomIdentity .translate(initialTranslate[0], initialTranslate[1]) - .scale(initialScale), + .scale(initialScale) ); // Fix center node - const centerNode = nodes.find(n => n.isCenter); - const categoryNodes = nodes.filter(n => !n.isCenter && n.stageid === -1); - + const centerNode = nodes.find((n) => n.isCenter); + const categoryNodes = nodes.filter((n) => !n.isCenter && n.stageid === -1); + if (centerNode) { const centerX = width / 2; const centerY = height / 2; centerNode.fx = centerX; centerNode.fy = centerY; - + const baseRadius = 450; // شعاع پایه const variation = 100; // تغییر طول یکی در میان const angleStep = (2 * Math.PI) / categoryNodes.length; - + categoryNodes.forEach((catNode, i) => { const angle = i * angleStep; const radius = baseRadius + (i % 2 === 0 ? -variation : variation); @@ -266,26 +294,24 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps catNode.fy = centerY + radius * Math.sin(angle); }); } - + // نودهای نهایی **هیچ fx/fy نداشته باشند** // فقط forceLink آن‌ها را به دسته‌ها متصل نگه می‌دارد - - -// const finalNodes = nodes.filter(n => !n.isCenter && n.stageid !== -1); -// categoryNodes.forEach((catNode) => { -// const childNodes = finalNodes.filter(n => n.category === catNode.category); -// const childCount = childNodes.length; -// const radius = 100; // فاصله از دسته -// const angleStep = (2 * Math.PI) / childCount; + // const finalNodes = nodes.filter(n => !n.isCenter && n.stageid !== -1); -// childNodes.forEach((node, i) => { -// const angle = i * angleStep; -// node.fx = catNode.fx! + radius * Math.cos(angle); -// node.fy = catNode.fy! + radius * Math.sin(angle); -// }); -// }); + // categoryNodes.forEach((catNode) => { + // const childNodes = finalNodes.filter(n => n.category === catNode.category); + // const childCount = childNodes.length; + // const radius = 100; // فاصله از دسته + // const angleStep = (2 * Math.PI) / childCount; + // childNodes.forEach((node, i) => { + // const angle = i * angleStep; + // node.fx = catNode.fx! + radius * Math.cos(angle); + // node.fy = catNode.fy! + radius * Math.sin(angle); + // }); + // }); // Curved links const link = container @@ -305,8 +331,8 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps .enter() .append("g") .attr("class", "node") - .style("cursor", d => d.stageid === -1 ? "default" : "pointer"); - + .style("cursor", (d) => (d.stageid === -1 ? "default" : "pointer")); + const drag = d3 .drag() .on("start", (event, d) => { @@ -337,7 +363,7 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps .attr("width", 200) .attr("height", 80) .attr("x", -100) // نصف عرض جدید منفی - .attr("y", -40) // نصف ارتفاع جدید منفی + .attr("y", -40) // نصف ارتفاع جدید منفی .attr("rx", 8) .attr("ry", 8) .attr("fill", categoryToColor[d.category] || "#94A3B8") @@ -358,7 +384,7 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps .append("image") .attr("x", 0) .attr("y", 0) - .attr("width", 200) // ← هم‌اندازه با مستطیل + .attr("width", 200) // ← هم‌اندازه با مستطیل .attr("height", 80) .attr("href", d.isCenter ? "/main-circle.png" : d.imageUrl) .attr("preserveAspectRatio", "xMidYMid slice"); @@ -437,12 +463,11 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps .attr("stroke-width", 3); }); - nodeGroup.on("click", async function (event, d) { event.stopPropagation(); - // جلوگیری از کلیک روی مرکز و دسته‌بندی‌ها - if (d.isCenter || d.stageid === -1) return; + // جلوگیری از کلیک روی مرکز و دسته‌بندی‌ها + if (d.isCenter || d.stageid === -1) return; if (onNodeClick && d.stageid) { // Open dialog immediately with basic info @@ -454,10 +479,10 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps fields: [], }; onNodeClick(basicDetails); - + // Start loading onLoadingChange?.(true); - + try { const res = await callAPI(d.stageid); const responseData = JSON.parse(res.data); @@ -467,15 +492,15 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps const filteredFields = fieldValues.filter( (field: any) => !["image", "img", "full_name", "about_collaboration"].includes( - field.F.toLowerCase(), - ), + field.F.toLowerCase() + ) ); const descriptionField = fieldValues.find( (field: any) => field.F.toLowerCase().includes("description") || field.F.toLowerCase().includes("about_collaboration") || - field.F.toLowerCase().includes("about"), + field.F.toLowerCase().includes("about") ); const companyDetails: CompanyDetails = { @@ -592,5 +617,4 @@ export function NetworkGraph({ onNodeClick, onLoadingChange }: NetworkGraphProps ); } - -export default NetworkGraph; \ No newline at end of file +export default NetworkGraph; diff --git a/app/components/ui/Calendar.tsx b/app/components/ui/Calendar.tsx index f05648e..493bf42 100644 --- a/app/components/ui/Calendar.tsx +++ b/app/components/ui/Calendar.tsx @@ -34,7 +34,7 @@ export const Calendar: React.FC = ({ selectDateHandler, }) => { return ( -
+
{title}
diff --git a/app/types/util.type.ts b/app/types/util.type.ts index da91223..74c802e 100644 --- a/app/types/util.type.ts +++ b/app/types/util.type.ts @@ -1,6 +1,6 @@ export interface CalendarDate { start: string; end: string; - sinceMonth: string; - untilMonth: string; + sinceMonth?: string; + untilMonth?: string; }