From 585e66570d3f3313b93b542e1f143ac3f333d967 Mon Sep 17 00:00:00 2001 From: Saeed Abadiyan Date: Tue, 23 Sep 2025 15:41:27 +0330 Subject: [PATCH] fix the customChartBar in dashboard and process-innvation, also fix the style in dashboard and ecosystem's popup --- app/components/dashboard/dashboard-home.tsx | 35 +++---- .../process-innovation-page.tsx | 7 +- .../product-innovation-page.tsx | 36 +++++--- .../project-management-page.tsx | 2 +- .../dashboard/strategic-alignment-popup.tsx | 20 ++-- app/components/ui/custom-bar-chart.tsx | 33 +++---- app/components/ui/progress.tsx | 11 ++- app/lib/utils.ts | 92 +++++++++++++++++++ app/routes/ecosystem.tsx | 14 ++- 9 files changed, 175 insertions(+), 75 deletions(-) diff --git a/app/components/dashboard/dashboard-home.tsx b/app/components/dashboard/dashboard-home.tsx index c1c4d83..35feda2 100644 --- a/app/components/dashboard/dashboard-home.tsx +++ b/app/components/dashboard/dashboard-home.tsx @@ -130,18 +130,18 @@ export function DashboardHome() { let incCapacityTotal = 0; const chartRows = rows.map((r) => { const rel = r?.related_company ?? "-"; - const preFee = Number(r?.pre_innovation_fee_sum ?? 0) > 0 ? r?.pre_innovation_fee_sum : 0; - const costRed = Number(r?.innovation_cost_reduction_sum ?? 0) > 0 ? r?.innovation_cost_reduction_sum : 0; - const preCap = Number(r?.pre_project_production_capacity_sum ?? 0) > 0 ? r?.pre_project_production_capacity_sum : 0; - const incCap = Number(r?.increased_capacity_after_innovation_sum ?? 0) > 0 ? r?.increased_capacity_after_innovation_sum : 0; - const preInc = Number(r?.pre_project_income_sum ?? 0) > 0 ? r?.pre_project_income_sum : 0; - const incInc = Number(r?.increased_income_after_innovation_sum ?? 0) > 0 ? r?.increased_income_after_innovation_sum : 0; + const preFee = Number(r?.pre_innovation_fee_sum ?? 0) >= 0 ? r?.pre_innovation_fee_sum : 0; + const costRed = Number(r?.innovation_cost_reduction_sum ?? 0) >= 0 ? r?.innovation_cost_reduction_sum : 0; + const preCap = Number(r?.pre_project_production_capacity_sum ?? 0) >= 0 ? r?.pre_project_production_capacity_sum : 0; + const incCap = Number(r?.increased_capacity_after_innovation_sum ?? 0) >= 0 ? r?.increased_capacity_after_innovation_sum : 0; + const preInc = Number(r?.pre_project_income_sum ?? 0) >= 0 ? r?.pre_project_income_sum : 0; + const incInc = Number(r?.increased_income_after_innovation_sum ?? 0) >= 0 ? r?.increased_income_after_innovation_sum : 0; incCapacityTotal += incCap; - const capacityPct = preCap > 0 ? (incCap / preCap) * 100 : 0; - const revenuePct = preInc > 0 ? (incInc / preInc) * 100 : 0; - const costPct = preFee > 0 ? (costRed / preFee) * 100 : 0; + const capacityPct = preCap >= 0 ? (incCap / preCap) * 100 : 0; + const revenuePct = preInc >= 0 ? (incInc / preInc) * 100 : 0; + const costPct = preFee >= 0 ? (costRed / preFee) * 100 : 0; return { category: rel, capacity: isFinite(capacityPct) ? capacityPct : 0, @@ -178,7 +178,7 @@ export function DashboardHome() { dashboardData.topData.ongoing_innovation_technology_ideas || "0", ); const percentage = - registered > 0 ? Math.round((ongoing / registered) * 100) : 0; + registered > 0 ? (ongoing / registered) * 100 : 0; return [ { browser: "safari", visitors: percentage, fill: "var(--color-safari)" }, @@ -461,7 +461,7 @@ export function DashboardHome() { @@ -469,7 +469,7 @@ export function DashboardHome() { @@ -646,17 +646,10 @@ export function DashboardHome() { شدت فناوری -

- % - {formatNumber( - Math.round( - dashboardData.leftData?.technology_intensity || 0, - ), - )} -

+ diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx index 048682d..6572ab2 100644 --- a/app/components/dashboard/project-management/process-innovation-page.tsx +++ b/app/components/dashboard/project-management/process-innovation-page.tsx @@ -260,7 +260,6 @@ export function ProcessInnovationPage() { Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); - console.log(JSON.parse(response.data)); if (response.state === 0) { const dataString = response.data; if (dataString && typeof dataString === "string") { @@ -656,6 +655,12 @@ export function ProcessInnovationPage() { {/* Process Impacts Chart */} + {/* نمودار با الگوریتم Nice Numbers: + مثلاً اگر داده‌ها [10, 35, 63, 18] باشند: + - حداکثر: 63، با حاشیه 5% = 66.15 + - Nice Max: 75 (گرد و خوانا) + - Ticks: [0, 20, 40, 60, 75] + این باعث می‌شود نمودار زیباتر و خوانا‌تر باشد */} x == valueTimeLine) + const per = () => { + const main = stages?.findIndex((x) => x == "ثبت ایده") + console.log( 'yay ' , 25 * main + 12.5); + return 25 * main + 12.5 + } return (
{/* Year labels */} @@ -151,7 +157,6 @@ export default function Timeline() { ۱۴۰۵ ۱۴۰۴
- {/* Timeline bar */}
{stages.map((stage, index) => ( @@ -171,15 +176,17 @@ export default function Timeline() { ))} {/* Vertical line showing current position */} -
0 && ( <>
وضعیت فعلی
-
+ ) } + +
); } @@ -343,6 +350,7 @@ export function ProductInnovationPage() { "project_no", "title", "project_status", + "current_status", "project_rating", "project_description", "developed_technology_type", @@ -350,7 +358,7 @@ export function ProductInnovationPage() { "knowledge_based_certificate_obtained", "knowledge_based_certificate_number", "certificate_obtain_date", - "issuing_authority", + "issuing_authority" ], Sorts: [["start_date", "asc"]], Conditions: [["type_of_innovation", "=", "نوآوری در محصول"]], @@ -458,8 +466,8 @@ export function ProductInnovationPage() { ...prev, revenueNewProducts: { ...prev.revenueNewProducts, - value: formatNumber(normalized.new_products_revenue_share), - percent: formatNumber(normalized.new_products_revenue_share_percent), + value: formatNumber(normalized?.new_products_revenue_share), + percent: formatNumber(normalized?.new_products_revenue_share_percent), }, impactOnImports: { ...prev.impactOnImports, @@ -708,8 +716,8 @@ export function ProductInnovationPage() {
@@ -936,7 +944,7 @@ export function ProductInnovationPage() {

{selectedProjectDetails?.title}

{selectedProjectDetails?.project_description}

- + {/* Technical Knowledge */}
diff --git a/app/components/dashboard/project-management/project-management-page.tsx b/app/components/dashboard/project-management/project-management-page.tsx index dca4735..8e76f6a 100644 --- a/app/components/dashboard/project-management/project-management-page.tsx +++ b/app/components/dashboard/project-management/project-management-page.tsx @@ -289,7 +289,7 @@ export function ProjectManagementPage() { const { scrollTop, scrollHeight, clientHeight } = scrollContainer; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; // Trigger load more when scrolled to 90% of the container - if (scrollPercentage == 1) { + if (scrollPercentage == 1 || scrollPercentage == .9) { loadMore(); } }; diff --git a/app/components/dashboard/strategic-alignment-popup.tsx b/app/components/dashboard/strategic-alignment-popup.tsx index 046538d..8769448 100644 --- a/app/components/dashboard/strategic-alignment-popup.tsx +++ b/app/components/dashboard/strategic-alignment-popup.tsx @@ -41,11 +41,11 @@ const chartConfig = { }, }; - const maxHeight = 150; + const maxHeight = 150; const barHeights = () => Math.floor(Math.random() * maxHeight); const ChartSkeleton = () => ( - +
{/* Chart bars */}
@@ -131,7 +131,7 @@ export function StrategicAlignmentPopup({ return ( - + میزان انطباق راهبردی @@ -161,7 +161,7 @@ export function StrategicAlignmentPopup({ return ( - @@ -182,14 +182,14 @@ export function StrategicAlignmentPopup({ label={{ - value: "تعداد برنامه ها" , - angle: -90, - position: "insideLeft", + value: "تعداد برنامه ها" , + angle: -90, + position: "insideLeft", fill: "#94a3b8", fontSize: 11, offset: 0, - dy: 0, - style: { textAnchor: "middle" }, + dy: 0, + style: { textAnchor: "middle" }, }} /> @@ -209,7 +209,7 @@ export function StrategicAlignmentPopup({ }} formatter={(v: number) => `${formatNumber(Math.round(v))}`} /> - + diff --git a/app/components/ui/custom-bar-chart.tsx b/app/components/ui/custom-bar-chart.tsx index b4d26d5..85844f8 100644 --- a/app/components/ui/custom-bar-chart.tsx +++ b/app/components/ui/custom-bar-chart.tsx @@ -1,4 +1,4 @@ -import { formatNumber } from "~/lib/utils"; +import { formatNumber, calculateNiceRange } from "~/lib/utils"; export interface BarChartData { label: string; @@ -29,10 +29,10 @@ export function CustomBarChart({ className = "", loading = false, }: CustomBarChartProps) { - // Calculate the maximum value across all data points for consistent scaling - const globalMaxValue = Math.max( - ...data.map((item) => item.maxValue || item.value) - ); + // استفاده از nice numbers برای محاسبه دامنه مناسب + const values = data.map((item) => item.maxValue || item.value); + const { niceMax, ticks } = calculateNiceRange(values, 0, 5); + const globalMaxValue = niceMax; // Loading skeleton if (loading) { @@ -77,6 +77,7 @@ export function CustomBarChart({
{data.map((item, index) => { + // محاسبه درصد بر اساس nice max value const percentage = globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0; const displayValue: any = item.value; @@ -106,7 +107,7 @@ export function CustomBarChart({ {item.valuePrefix || ""} - {formatNumber(parseFloat(displayValue))} + {formatNumber(parseFloat(displayValue))}% {item.valueSuffix || ""}
@@ -114,24 +115,16 @@ export function CustomBarChart({ ); })} - {/* Axis Labels */} + {/* Axis Labels با استفاده از nice numbers */} {showAxisLabels && globalMaxValue > 0 && (
- {formatNumber(0)} - - {formatNumber(Math.round(globalMaxValue / 4))} - - - {formatNumber(Math.round(globalMaxValue / 2))} - - - {formatNumber(Math.round((globalMaxValue * 3) / 4))} - - - {formatNumber(Math.round(globalMaxValue))} - + {ticks.map((tick, index) => ( + + {formatNumber(tick)}% + + ))}
diff --git a/app/components/ui/progress.tsx b/app/components/ui/progress.tsx index 79a55bd..8e11734 100644 --- a/app/components/ui/progress.tsx +++ b/app/components/ui/progress.tsx @@ -1,7 +1,7 @@ import * as React from "react" import * as ProgressPrimitive from "@radix-ui/react-progress" -import { cn } from "~/lib/utils" +import { cn, formatNumber } from "~/lib/utils" const Progress = React.forwardRef< React.ElementRef, @@ -10,14 +10,19 @@ const Progress = React.forwardRef< + ۰% + {formatNumber(Math.ceil(value || 0 * 10) / 10)}% + {formatNumber(.2)}% )) diff --git a/app/lib/utils.ts b/app/lib/utils.ts index 2bc7fa1..8d032cc 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -24,6 +24,98 @@ export const formatCurrency = (amount: string | number) => { +/** + * محاسبه دامنه nice numbers برای محور Y نمودارها + * @param values آرایه از مقادیر داده‌ها + * @param minValue حداقل مقدار (پیش‌فرض: 0 برای داده‌های درصدی) + * @param marginPercent درصد حاشیه اضافی (پیش‌فرض: 5%) + * @returns شیء شامل حداکثر nice، فاصله tick ها، و آرایه tick ها + */ +export function calculateNiceRange( + values: number[], + minValue: number = 0, + marginPercent: number = 5 +): { + niceMax: number; + tickInterval: number; + ticks: number[]; +} { + if (values.length === 0) { + return { niceMax: 100, tickInterval: 20, ticks: [0, 20, 40, 60, 80, 100] }; + } + + // پیدا کردن حداکثر مقدار در داده‌ها + const dataMax = Math.max(...values); + + // اگر همه مقادیر صفر یا منفی هستند + if (dataMax <= 0) { + return { niceMax: 100, tickInterval: 20, ticks: [0, 20, 40, 60, 80, 100] }; + } + + // اضافه کردن حاشیه + const maxWithMargin = dataMax * (1 + marginPercent / 100); + + // محاسبه nice upper limit + const niceMax = calculateNiceNumber(maxWithMargin, true); + + // محاسبه فاصله مناسب tick ها بر اساس niceMax + const range = niceMax - minValue; + const targetTicks = 5; // هدف: 5 tick + const roughTickInterval = range / (targetTicks - 1); + const niceTickInterval = calculateNiceNumber(roughTickInterval, false); + + // ایجاد آرایه tick ها + const ticks: number[] = []; + for (let i = minValue; i <= niceMax; i += niceTickInterval) { + ticks.push(Math.round(i)); + } + + // اطمینان از اینکه niceMax در آرایه tick ها باشد + if (ticks[ticks.length - 1] !== niceMax) { + ticks.push(niceMax); + } + + return { + niceMax, + tickInterval: niceTickInterval, + ticks, + }; +} + +/** + * محاسبه عدد nice (گرد و خوانا) بر اساس الگوریتم nice numbers + * @param value مقدار ورودی + * @param round آیا به سمت بالا گرد شود یا نه + * @returns عدد nice + */ +function calculateNiceNumber(value: number, round: boolean): number { + if (value <= 0) return 0; + + // پیدا کردن قدرت 10 + const exponent = Math.floor(Math.log10(value)); + const fraction = value / Math.pow(10, exponent); + + let niceFraction: number; + + if (round) { + // برای حداکثر: به سمت بالا گرد می‌کنیم با دقت بیشتر + if (fraction <= 1.0) niceFraction = 1; + else if (fraction <= 2.0) niceFraction = 2; + else if (fraction <= 2.5) niceFraction = 2.5; + else if (fraction <= 5.0) niceFraction = 5; + else if (fraction <= 7.5) niceFraction = 7.5; + else niceFraction = 10; + } else { + // برای فاصله tick ها: اعداد ساده‌تر + if (fraction <= 1.0) niceFraction = 1; + else if (fraction <= 2.0) niceFraction = 2; + else if (fraction <= 5.0) niceFraction = 5; + else niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); +} + export const handleDataValue = (val: any): any => { moment.loadPersian({ usePersianDigits: true }); if (val == null) return val; diff --git a/app/routes/ecosystem.tsx b/app/routes/ecosystem.tsx index 69ecb78..f0c53b4 100644 --- a/app/routes/ecosystem.tsx +++ b/app/routes/ecosystem.tsx @@ -22,6 +22,7 @@ const API_BASE_URL = // Import the CompanyDetails type import type { CompanyDetails } from "~/components/ecosystem/network-graph"; import { formatNumber } from "~/lib/utils"; +import { Hexagon } from "lucide-react"; export function meta({}: Route.MetaArgs) { return [ @@ -164,18 +165,21 @@ export default function EcosystemPage() { {selectedCompany?.fields && selectedCompany.fields.length > 0 ? ( -
+
{selectedCompany.fields.map((field, index) => (
- + + {field.N}: - - {handleValue(field.V)} - {field.U && ({field.U})} + + + {handleValue(field.V)} + {field.U && ({field.U})} +
))}