fix the customChartBar in dashboard and process-innvation, also fix the style in dashboard and ecosystem's popup
This commit is contained in:
parent
1a0cf20319
commit
585e66570d
|
|
@ -130,18 +130,18 @@ export function DashboardHome() {
|
||||||
let incCapacityTotal = 0;
|
let incCapacityTotal = 0;
|
||||||
const chartRows = rows.map((r) => {
|
const chartRows = rows.map((r) => {
|
||||||
const rel = r?.related_company ?? "-";
|
const rel = r?.related_company ?? "-";
|
||||||
const preFee = Number(r?.pre_innovation_fee_sum ?? 0) > 0 ? r?.pre_innovation_fee_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 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 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 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 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 incInc = Number(r?.increased_income_after_innovation_sum ?? 0) >= 0 ? r?.increased_income_after_innovation_sum : 0;
|
||||||
|
|
||||||
incCapacityTotal += incCap;
|
incCapacityTotal += incCap;
|
||||||
|
|
||||||
const capacityPct = preCap > 0 ? (incCap / preCap) * 100 : 0;
|
const capacityPct = preCap >= 0 ? (incCap / preCap) * 100 : 0;
|
||||||
const revenuePct = preInc > 0 ? (incInc / preInc) * 100 : 0;
|
const revenuePct = preInc >= 0 ? (incInc / preInc) * 100 : 0;
|
||||||
const costPct = preFee > 0 ? (costRed / preFee) * 100 : 0;
|
const costPct = preFee >= 0 ? (costRed / preFee) * 100 : 0;
|
||||||
return {
|
return {
|
||||||
category: rel,
|
category: rel,
|
||||||
capacity: isFinite(capacityPct) ? capacityPct : 0,
|
capacity: isFinite(capacityPct) ? capacityPct : 0,
|
||||||
|
|
@ -178,7 +178,7 @@ export function DashboardHome() {
|
||||||
dashboardData.topData.ongoing_innovation_technology_ideas || "0",
|
dashboardData.topData.ongoing_innovation_technology_ideas || "0",
|
||||||
);
|
);
|
||||||
const percentage =
|
const percentage =
|
||||||
registered > 0 ? Math.round((ongoing / registered) * 100) : 0;
|
registered > 0 ? (ongoing / registered) * 100 : 0;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" },
|
{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" },
|
||||||
|
|
@ -461,7 +461,7 @@ export function DashboardHome() {
|
||||||
<MetricCard
|
<MetricCard
|
||||||
title="افزایش درآمد مبتنی بر فناوری و نوآوری"
|
title="افزایش درآمد مبتنی بر فناوری و نوآوری"
|
||||||
value={dashboardData.topData?.technology_innovation_based_revenue_growth || "0"}
|
value={dashboardData.topData?.technology_innovation_based_revenue_growth || "0"}
|
||||||
percentValue={Math.round(dashboardData.topData?.technology_innovation_based_revenue_growth_percent) || "0"}
|
percentValue={dashboardData.topData?.technology_innovation_based_revenue_growth_percent}
|
||||||
percentLabel="درصد به کل درآمد"
|
percentLabel="درصد به کل درآمد"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -469,7 +469,7 @@ export function DashboardHome() {
|
||||||
<MetricCard
|
<MetricCard
|
||||||
title="کاهش هزینه ها مبتنی بر فناوری و نوآوری"
|
title="کاهش هزینه ها مبتنی بر فناوری و نوآوری"
|
||||||
value={Math.round(parseFloat(dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(/,/g, "") || "0") / 1000000)}
|
value={Math.round(parseFloat(dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(/,/g, "") || "0") / 1000000)}
|
||||||
percentValue={Math.round(dashboardData.topData?.technology_innovation_based_cost_reduction_percent) || "0"}
|
percentValue={dashboardData.topData?.technology_innovation_based_cost_reduction_percent || "0"}
|
||||||
percentLabel="درصد به کل هزینه"
|
percentLabel="درصد به کل هزینه"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -646,17 +646,10 @@ export function DashboardHome() {
|
||||||
<CardTitle className="text-white text-sm min-w-[100px]">
|
<CardTitle className="text-white text-sm min-w-[100px]">
|
||||||
شدت فناوری
|
شدت فناوری
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<p className="text-base text-left">
|
|
||||||
%
|
|
||||||
{formatNumber(
|
|
||||||
Math.round(
|
|
||||||
dashboardData.leftData?.technology_intensity || 0,
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<Progress
|
<Progress
|
||||||
value={parseFloat(
|
value={parseFloat(
|
||||||
dashboardData.leftData?.technology_intensity || "0",
|
dashboardData.leftData?.technology_intensity,
|
||||||
)}
|
)}
|
||||||
className="h-4 flex-1"
|
className="h-4 flex-1"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -260,7 +260,6 @@ export function ProcessInnovationPage() {
|
||||||
Pagination: { PageNumber: pageToFetch, PageSize: pageSize },
|
Pagination: { PageNumber: pageToFetch, PageSize: pageSize },
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(JSON.parse(response.data));
|
|
||||||
if (response.state === 0) {
|
if (response.state === 0) {
|
||||||
const dataString = response.data;
|
const dataString = response.data;
|
||||||
if (dataString && typeof dataString === "string") {
|
if (dataString && typeof dataString === "string") {
|
||||||
|
|
@ -656,6 +655,12 @@ export function ProcessInnovationPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Process Impacts Chart */}
|
{/* Process Impacts Chart */}
|
||||||
|
{/* نمودار با الگوریتم Nice Numbers:
|
||||||
|
مثلاً اگر دادهها [10, 35, 63, 18] باشند:
|
||||||
|
- حداکثر: 63، با حاشیه 5% = 66.15
|
||||||
|
- Nice Max: 75 (گرد و خوانا)
|
||||||
|
- Ticks: [0, 20, 40, 60, 75]
|
||||||
|
این باعث میشود نمودار زیباتر و خواناتر باشد */}
|
||||||
<BaseCard className="rounded-2xl w-full overflow-hidden">
|
<BaseCard className="rounded-2xl w-full overflow-hidden">
|
||||||
<CustomBarChart
|
<CustomBarChart
|
||||||
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
|
title="تاثیرات فرآیندی به صورت درصد مقایسه ای"
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ interface ProjectData {
|
||||||
project_id: string;
|
project_id: string;
|
||||||
title: string;
|
title: string;
|
||||||
project_status: string;
|
project_status: string;
|
||||||
|
current_status?: string;
|
||||||
project_rating: string;
|
project_rating: string;
|
||||||
project_description: string;
|
project_description: string;
|
||||||
developed_technology_type: string;
|
developed_technology_type: string;
|
||||||
|
|
@ -94,6 +95,7 @@ interface ProductInnovationData {
|
||||||
title: string;
|
title: string;
|
||||||
project_status: projectStatus;
|
project_status: projectStatus;
|
||||||
project_rating: string;
|
project_rating: string;
|
||||||
|
current_status?: string;
|
||||||
project_description: string;
|
project_description: string;
|
||||||
developed_technology_type: string;
|
developed_technology_type: string;
|
||||||
obtained_standard_title: string;
|
obtained_standard_title: string;
|
||||||
|
|
@ -138,10 +140,14 @@ const columns = [
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export default function Timeline() {
|
export default function Timeline( valueTimeLine : string) {
|
||||||
const stages = ["تجاری سازی", "توسعه", "تحلیل بازار", "ثبت ایده"];
|
const stages = ["تجاری سازی", "توسعه", "تحلیل بازار", "ثبت ایده"];
|
||||||
const currentStage = 1; // index of current stage
|
const currentStage = stages?.toReversed()?.findIndex((x : string) => x == valueTimeLine)
|
||||||
|
const per = () => {
|
||||||
|
const main = stages?.findIndex((x) => x == "ثبت ایده")
|
||||||
|
console.log( 'yay ' , 25 * main + 12.5);
|
||||||
|
return 25 * main + 12.5
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-4">
|
<div className="w-full p-4">
|
||||||
{/* Year labels */}
|
{/* Year labels */}
|
||||||
|
|
@ -151,7 +157,6 @@ export default function Timeline() {
|
||||||
<span>۱۴۰۵</span>
|
<span>۱۴۰۵</span>
|
||||||
<span>۱۴۰۴</span>
|
<span>۱۴۰۴</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Timeline bar */}
|
{/* Timeline bar */}
|
||||||
<div className="relative rounded-lg flex mb-4 items-center">
|
<div className="relative rounded-lg flex mb-4 items-center">
|
||||||
{stages.map((stage, index) => (
|
{stages.map((stage, index) => (
|
||||||
|
|
@ -171,15 +176,17 @@ export default function Timeline() {
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Vertical line showing current position */}
|
{/* Vertical line showing current position */}
|
||||||
<div
|
{ valueTimeLine?.length > 0 && ( <> <div
|
||||||
className="absolute left-[37%] top-0 h-[150%] bottom-0 w-[2px] bg-white rounded-full"
|
className={`absolute top-0 h-[150%] bottom-0 w-[2px] bg-white rounded-full`}
|
||||||
style={{ left: `${(currentStage + 0.5) * (100 / stages.length)}%` }}
|
style={{ left: `${(currentStage + 0.5) * (100 / stages.length)}%` }}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="absolute top-15 h-[max-content] translate-x-[-50%] text-xs text-gray-300 border-gray-400 rounded-md border px-2 bottom-0"
|
className="absolute top-15 h-[max-content] translate-x-[-50%] text-xs text-gray-300 border-gray-400 rounded-md border px-2 bottom-0"
|
||||||
style={{ left: `${(currentStage + 0.5) * (100 / stages.length)}%` }}
|
style={{ left: `${(currentStage + 0.5) * (100 / stages.length)}%` }}
|
||||||
>وضعیت فعلی</div>
|
>وضعیت فعلی</div>
|
||||||
</div>
|
</> ) }
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -343,6 +350,7 @@ export function ProductInnovationPage() {
|
||||||
"project_no",
|
"project_no",
|
||||||
"title",
|
"title",
|
||||||
"project_status",
|
"project_status",
|
||||||
|
"current_status",
|
||||||
"project_rating",
|
"project_rating",
|
||||||
"project_description",
|
"project_description",
|
||||||
"developed_technology_type",
|
"developed_technology_type",
|
||||||
|
|
@ -350,7 +358,7 @@ export function ProductInnovationPage() {
|
||||||
"knowledge_based_certificate_obtained",
|
"knowledge_based_certificate_obtained",
|
||||||
"knowledge_based_certificate_number",
|
"knowledge_based_certificate_number",
|
||||||
"certificate_obtain_date",
|
"certificate_obtain_date",
|
||||||
"issuing_authority",
|
"issuing_authority"
|
||||||
],
|
],
|
||||||
Sorts: [["start_date", "asc"]],
|
Sorts: [["start_date", "asc"]],
|
||||||
Conditions: [["type_of_innovation", "=", "نوآوری در محصول"]],
|
Conditions: [["type_of_innovation", "=", "نوآوری در محصول"]],
|
||||||
|
|
@ -458,8 +466,8 @@ export function ProductInnovationPage() {
|
||||||
...prev,
|
...prev,
|
||||||
revenueNewProducts: {
|
revenueNewProducts: {
|
||||||
...prev.revenueNewProducts,
|
...prev.revenueNewProducts,
|
||||||
value: formatNumber(normalized.new_products_revenue_share),
|
value: formatNumber(normalized?.new_products_revenue_share),
|
||||||
percent: formatNumber(normalized.new_products_revenue_share_percent),
|
percent: formatNumber(normalized?.new_products_revenue_share_percent),
|
||||||
},
|
},
|
||||||
impactOnImports: {
|
impactOnImports: {
|
||||||
...prev.impactOnImports,
|
...prev.impactOnImports,
|
||||||
|
|
@ -708,8 +716,8 @@ export function ProductInnovationPage() {
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<MetricCard
|
<MetricCard
|
||||||
title={stateCard.revenueNewProducts.title}
|
title={stateCard.revenueNewProducts.title}
|
||||||
value={stateCard.revenueNewProducts.value}
|
value={formatNumber(stateCard.revenueNewProducts.value)}
|
||||||
percentValue={stateCard.revenueNewProducts.percent}
|
percentValue={formatNumber(stateCard.revenueNewProducts.percent)}
|
||||||
valueLabel={stateCard.revenueNewProducts.description}
|
valueLabel={stateCard.revenueNewProducts.description}
|
||||||
percentLabel={stateCard.revenueNewProducts.descriptionPercent}
|
percentLabel={stateCard.revenueNewProducts.descriptionPercent}
|
||||||
/>
|
/>
|
||||||
|
|
@ -936,7 +944,7 @@ export function ProductInnovationPage() {
|
||||||
<h3 className="font-bold text-base">{selectedProjectDetails?.title}</h3>
|
<h3 className="font-bold text-base">{selectedProjectDetails?.title}</h3>
|
||||||
<p className="py-2">{selectedProjectDetails?.project_description}</p>
|
<p className="py-2">{selectedProjectDetails?.project_description}</p>
|
||||||
</div>
|
</div>
|
||||||
<Timeline />
|
<Timeline valueTimeLine={selectedProjectDetails?.current_status} />
|
||||||
|
|
||||||
{/* Technical Knowledge */}
|
{/* Technical Knowledge */}
|
||||||
<div className=" rounded-lg py-2 mb-0">
|
<div className=" rounded-lg py-2 mb-0">
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ export function ProjectManagementPage() {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
||||||
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
|
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
|
||||||
// Trigger load more when scrolled to 90% of the container
|
// Trigger load more when scrolled to 90% of the container
|
||||||
if (scrollPercentage == 1) {
|
if (scrollPercentage == 1 || scrollPercentage == .9) {
|
||||||
loadMore();
|
loadMore();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ export function StrategicAlignmentPopup({
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="w-full max-w-4xl bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] text-white border-none">
|
<DialogContent className="w-full max-w-4xl bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] text-white border-none">
|
||||||
<DialogHeader className="border-b-3 mb-10 py-2 w-full pb-4 border-b-2 border-gray-500/20">
|
<DialogHeader className="mb-10 py-2 w-full pb-4 border-b-2 border-gray-500/20">
|
||||||
<DialogTitle className="ml-auto text-sm text-white ">میزان انطباق راهبردی</DialogTitle>
|
<DialogTitle className="ml-auto text-sm text-white ">میزان انطباق راهبردی</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { formatNumber } from "~/lib/utils";
|
import { formatNumber, calculateNiceRange } from "~/lib/utils";
|
||||||
|
|
||||||
export interface BarChartData {
|
export interface BarChartData {
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -29,10 +29,10 @@ export function CustomBarChart({
|
||||||
className = "",
|
className = "",
|
||||||
loading = false,
|
loading = false,
|
||||||
}: CustomBarChartProps) {
|
}: CustomBarChartProps) {
|
||||||
// Calculate the maximum value across all data points for consistent scaling
|
// استفاده از nice numbers برای محاسبه دامنه مناسب
|
||||||
const globalMaxValue = Math.max(
|
const values = data.map((item) => item.maxValue || item.value);
|
||||||
...data.map((item) => item.maxValue || item.value)
|
const { niceMax, ticks } = calculateNiceRange(values, 0, 5);
|
||||||
);
|
const globalMaxValue = niceMax;
|
||||||
|
|
||||||
// Loading skeleton
|
// Loading skeleton
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|
@ -77,6 +77,7 @@ export function CustomBarChart({
|
||||||
|
|
||||||
<div className="space-y-4 px-4 pb-4">
|
<div className="space-y-4 px-4 pb-4">
|
||||||
{data.map((item, index) => {
|
{data.map((item, index) => {
|
||||||
|
// محاسبه درصد بر اساس nice max value
|
||||||
const percentage =
|
const percentage =
|
||||||
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
||||||
const displayValue: any = item.value;
|
const displayValue: any = item.value;
|
||||||
|
|
@ -106,7 +107,7 @@ export function CustomBarChart({
|
||||||
<span className={`text-base font-normal text-left text-white`}>
|
<span className={`text-base font-normal text-left text-white`}>
|
||||||
{item.valuePrefix || ""}
|
{item.valuePrefix || ""}
|
||||||
|
|
||||||
{formatNumber(parseFloat(displayValue))}
|
{formatNumber(parseFloat(displayValue))}%
|
||||||
{item.valueSuffix || ""}
|
{item.valueSuffix || ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -114,24 +115,16 @@ export function CustomBarChart({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* Axis Labels */}
|
{/* Axis Labels با استفاده از nice numbers */}
|
||||||
{showAxisLabels && globalMaxValue > 0 && (
|
{showAxisLabels && globalMaxValue > 0 && (
|
||||||
<div className="flex w-full items-center gap-3 mt-6">
|
<div className="flex w-full items-center gap-3 mt-6">
|
||||||
<span className="min-w-[120px]"></span>
|
<span className="min-w-[120px]"></span>
|
||||||
<div className="flex-1 flex justify-between pt-2 border-t border-gray-700">
|
<div className="flex-1 flex justify-between pt-2 border-t border-gray-700">
|
||||||
<span className="text-gray-400 text-xs">{formatNumber(0)}</span>
|
{ticks.map((tick, index) => (
|
||||||
<span className="text-gray-400 text-xs">
|
<span key={index} className="text-gray-400 text-xs">
|
||||||
{formatNumber(Math.round(globalMaxValue / 4))}
|
{formatNumber(tick)}%
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-xs">
|
))}
|
||||||
{formatNumber(Math.round(globalMaxValue / 2))}
|
|
||||||
</span>
|
|
||||||
<span className="text-gray-400 text-xs">
|
|
||||||
{formatNumber(Math.round((globalMaxValue * 3) / 4))}
|
|
||||||
</span>
|
|
||||||
<span className="text-gray-400 text-xs">
|
|
||||||
{formatNumber(Math.round(globalMaxValue))}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="min-w-[0px]"></span>
|
<span className="min-w-[0px]"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
import { cn } from "~/lib/utils"
|
import { cn, formatNumber } from "~/lib/utils"
|
||||||
|
|
||||||
const Progress = React.forwardRef<
|
const Progress = React.forwardRef<
|
||||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
|
@ -10,14 +10,19 @@ const Progress = React.forwardRef<
|
||||||
<ProgressPrimitive.Root
|
<ProgressPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
"relative h-4 w-full overflow-hidden rounded-full bg-pr-gray",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
<span className="left-0 text-sm absolute z-10 px-2 text-[#5F6284]">۰%</span>
|
||||||
|
<span className="w-full text-sm absolute z-10 px-2 text-[#5F6284]"
|
||||||
|
style={{ transform: `translateX(-${10 - (value || 0)}%)` }}
|
||||||
|
>{formatNumber(Math.ceil(value || 0 * 10) / 10)}%</span>
|
||||||
|
<span className="right-0 text-sm absolute z-10 px-2 text-[#5F6284]">{formatNumber(.2)}%</span>
|
||||||
<ProgressPrimitive.Indicator
|
<ProgressPrimitive.Indicator
|
||||||
className="h-full w-full flex-1 bg-primary transition-all"
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
style={{ transform: `translateX(-${20 - (value || 0)}%)` }}
|
||||||
/>
|
/>
|
||||||
</ProgressPrimitive.Root>
|
</ProgressPrimitive.Root>
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
export const handleDataValue = (val: any): any => {
|
||||||
moment.loadPersian({ usePersianDigits: true });
|
moment.loadPersian({ usePersianDigits: true });
|
||||||
if (val == null) return val;
|
if (val == null) return val;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ const API_BASE_URL =
|
||||||
// Import the CompanyDetails type
|
// Import the CompanyDetails type
|
||||||
import type { CompanyDetails } from "~/components/ecosystem/network-graph";
|
import type { CompanyDetails } from "~/components/ecosystem/network-graph";
|
||||||
import { formatNumber } from "~/lib/utils";
|
import { formatNumber } from "~/lib/utils";
|
||||||
|
import { Hexagon } from "lucide-react";
|
||||||
|
|
||||||
export function meta({}: Route.MetaArgs) {
|
export function meta({}: Route.MetaArgs) {
|
||||||
return [
|
return [
|
||||||
|
|
@ -164,18 +165,21 @@ export default function EcosystemPage() {
|
||||||
</h3>
|
</h3>
|
||||||
{selectedCompany?.fields &&
|
{selectedCompany?.fields &&
|
||||||
selectedCompany.fields.length > 0 ? (
|
selectedCompany.fields.length > 0 ? (
|
||||||
<div className="space-y-3 px-4">
|
<div className="space-y-3 px-2">
|
||||||
{selectedCompany.fields.map((field, index) => (
|
{selectedCompany.fields.map((field, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex justify-between items-center rounded-lg"
|
className="flex justify-between items-center rounded-lg"
|
||||||
>
|
>
|
||||||
<span className="font-persian text-sm font-light">
|
<span className="font-persian flex items-center gap-1 text-sm font-light">
|
||||||
|
<Hexagon className="text-pr-green h-4 w-4" />
|
||||||
{field.N}:
|
{field.N}:
|
||||||
</span>
|
</span>
|
||||||
<span className="font-persian text-sm font-normal text-right">
|
<span className="text-right min-w-1/3">
|
||||||
{handleValue(field.V)}
|
<span className="font-persian text-sm font-normal text-right">
|
||||||
{field.U && <span className="mr-1">({field.U})</span>}
|
{handleValue(field.V)}
|
||||||
|
{field.U && <span className="mr-1">({field.U})</span>}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user