feat: completed designed
This commit is contained in:
parent
584450550b
commit
173176bbb5
|
|
@ -1,38 +1,6 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { DashboardLayout } from "./layout";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { Progress } from "~/components/ui/progress";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
LineChart,
|
||||
Line,
|
||||
} from "recharts";
|
||||
import apiService from "~/lib/api";
|
||||
import { Book, CheckCircle } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
Calendar,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Target,
|
||||
Lightbulb,
|
||||
DollarSign,
|
||||
Minus,
|
||||
CheckCircle,
|
||||
Book,
|
||||
} from "lucide-react";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
|
||||
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
||||
import { DashboardCustomBarChart } from "./dashboard-custom-bar-chart";
|
||||
import { InteractiveBarChart } from "./interactive-bar-chart";
|
||||
import { D3ImageInfo } from "./d3-image-info";
|
||||
import {
|
||||
Label,
|
||||
PolarGrid,
|
||||
|
|
@ -40,10 +8,20 @@ import {
|
|||
RadialBar,
|
||||
RadialBarChart,
|
||||
} from "recharts";
|
||||
import { ChartContainer } from "~/components/ui/chart";
|
||||
import { formatNumber } from "~/lib/utils";
|
||||
import { MetricCard } from "~/components/ui/metric-card";
|
||||
import { BaseCard } from "~/components/ui/base-card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { ChartContainer } from "~/components/ui/chart";
|
||||
import { MetricCard } from "~/components/ui/metric-card";
|
||||
import { Progress } from "~/components/ui/progress";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
||||
import apiService from "~/lib/api";
|
||||
import { EventBus, formatNumber } from "~/lib/utils";
|
||||
import type { CalendarDate } from "~/types/util.type";
|
||||
import { D3ImageInfo } from "./d3-image-info";
|
||||
import { DashboardCustomBarChart } from "./dashboard-custom-bar-chart";
|
||||
import { InteractiveBarChart } from "./interactive-bar-chart";
|
||||
import { DashboardLayout } from "./layout";
|
||||
|
||||
export function DashboardHome() {
|
||||
const [dashboardData, setDashboardData] = useState<any | null>(null);
|
||||
|
|
@ -51,17 +29,30 @@ export function DashboardHome() {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
// Chart and schematic data from select API
|
||||
const [companyChartData, setCompanyChartData] = useState<
|
||||
{ category: string; capacity: number; revenue: number; cost: number , costI : number,
|
||||
capacityI : number,
|
||||
revenueI : number }[]
|
||||
{
|
||||
category: string;
|
||||
capacity: number;
|
||||
revenue: number;
|
||||
cost: number;
|
||||
costI: number;
|
||||
capacityI: number;
|
||||
revenueI: number;
|
||||
}[]
|
||||
>([]);
|
||||
const [totalIncreasedCapacity, setTotalIncreasedCapacity] = useState<number>(0);
|
||||
// const [totalIncreasedCapacity, setTotalIncreasedCapacity] =
|
||||
// useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDashboardData();
|
||||
}, []);
|
||||
|
||||
const fetchDashboardData = async () => {
|
||||
useEffect(() => {
|
||||
EventBus.on("dateSelected", (date: CalendarDate) => {
|
||||
if (date) fetchDashboardData(date.start, date.end);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fetchDashboardData = async (startDate?: string, endDate?: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
|
@ -74,12 +65,18 @@ export function DashboardHome() {
|
|||
|
||||
// Fetch top cards data
|
||||
const topCardsResponse = await apiService.call({
|
||||
main_page_first_function: {},
|
||||
main_page_first_function: {
|
||||
start_date: startDate || null,
|
||||
end_date: endDate || null,
|
||||
},
|
||||
});
|
||||
|
||||
// Fetch left section data
|
||||
const leftCardsResponse = await apiService.call({
|
||||
main_page_second_function: {},
|
||||
main_page_second_function: {
|
||||
start_date: startDate || null,
|
||||
end_date: endDate || null,
|
||||
},
|
||||
});
|
||||
|
||||
const topCardsResponseData = JSON.parse(topCardsResponse?.data);
|
||||
|
|
@ -130,12 +127,30 @@ 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;
|
||||
|
||||
|
|
@ -147,14 +162,14 @@ export function DashboardHome() {
|
|||
capacity: isFinite(capacityPct) ? capacityPct : 0,
|
||||
revenue: isFinite(revenuePct) ? revenuePct : 0,
|
||||
cost: isFinite(costPct) ? costPct : 0,
|
||||
costI : costRed,
|
||||
capacityI : incCap,
|
||||
revenueI : incInc
|
||||
costI: costRed,
|
||||
capacityI: incCap,
|
||||
revenueI: incInc,
|
||||
};
|
||||
});
|
||||
|
||||
setCompanyChartData(chartRows);
|
||||
setTotalIncreasedCapacity(incCapacityTotal);
|
||||
// setTotalIncreasedCapacity(incCapacityTotal);
|
||||
} catch (error) {
|
||||
console.error("Error fetching dashboard data:", error);
|
||||
const errorMessage =
|
||||
|
|
@ -172,20 +187,19 @@ export function DashboardHome() {
|
|||
return [{ browser: "safari", visitors: 0, fill: "var(--color-safari)" }];
|
||||
|
||||
const registered = parseFloat(
|
||||
dashboardData.topData.registered_innovation_technology_idea || "0",
|
||||
dashboardData.topData.registered_innovation_technology_idea || "0"
|
||||
);
|
||||
const ongoing = parseFloat(
|
||||
dashboardData.topData.ongoing_innovation_technology_ideas || "0",
|
||||
dashboardData.topData.ongoing_innovation_technology_ideas || "0"
|
||||
);
|
||||
const percentage =
|
||||
registered > 0 ? (ongoing / registered) * 100 : 0;
|
||||
const percentage = registered > 0 ? (ongoing / registered) * 100 : 0;
|
||||
|
||||
return [
|
||||
{ browser: "safari", visitors: percentage, fill: "var(--color-safari)" },
|
||||
];
|
||||
};
|
||||
|
||||
const chartData = getIdeasChartData();
|
||||
// const chartData = getIdeasChartData();
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
|
|
@ -329,20 +343,19 @@ export function DashboardHome() {
|
|||
visitors:
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea || "0",
|
||||
?.registered_innovation_technology_idea || "0"
|
||||
) > 0
|
||||
? Math.round(
|
||||
(parseFloat(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas ||
|
||||
"0",
|
||||
?.ongoing_innovation_technology_ideas || "0"
|
||||
) /
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"1",
|
||||
"1"
|
||||
)) *
|
||||
100,
|
||||
100
|
||||
)
|
||||
: 0,
|
||||
fill: "var(--color-green)",
|
||||
|
|
@ -353,19 +366,18 @@ export function DashboardHome() {
|
|||
90 +
|
||||
((parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea || "0",
|
||||
?.registered_innovation_technology_idea || "0"
|
||||
) > 0
|
||||
? Math.round(
|
||||
(parseFloat(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas || "0",
|
||||
?.ongoing_innovation_technology_ideas || "0"
|
||||
) /
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"1",
|
||||
?.registered_innovation_technology_idea || "1"
|
||||
)) *
|
||||
100,
|
||||
100
|
||||
)
|
||||
: 0) /
|
||||
100) *
|
||||
|
|
@ -381,11 +393,7 @@ export function DashboardHome() {
|
|||
className="first:fill-pr-red last:fill-[#24273A]"
|
||||
polarRadius={[38, 31]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="visitors"
|
||||
background
|
||||
cornerRadius={5}
|
||||
/>
|
||||
<RadialBar dataKey="visitors" background cornerRadius={5} />
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
|
|
@ -411,22 +419,22 @@ export function DashboardHome() {
|
|||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"0",
|
||||
"0"
|
||||
) > 0
|
||||
? Math.round(
|
||||
(parseFloat(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas ||
|
||||
"0",
|
||||
"0"
|
||||
) /
|
||||
parseFloat(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea ||
|
||||
"1",
|
||||
"1"
|
||||
)) *
|
||||
100,
|
||||
100
|
||||
)
|
||||
: 0,
|
||||
: 0
|
||||
)}
|
||||
</tspan>
|
||||
</text>
|
||||
|
|
@ -443,14 +451,14 @@ export function DashboardHome() {
|
|||
<div className="font-light text-sm">ثبت شده :</div>
|
||||
{formatNumber(
|
||||
dashboardData.topData
|
||||
?.registered_innovation_technology_idea || "0",
|
||||
?.registered_innovation_technology_idea || "0"
|
||||
)}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 font-bold text-base">
|
||||
<div className="font-light text-sm">در حال اجرا :</div>
|
||||
{formatNumber(
|
||||
dashboardData.topData
|
||||
?.ongoing_innovation_technology_ideas || "0",
|
||||
?.ongoing_innovation_technology_ideas || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -460,16 +468,34 @@ export function DashboardHome() {
|
|||
{/* Revenue Card */}
|
||||
<MetricCard
|
||||
title="افزایش درآمد مبتنی بر فناوری و نوآوری"
|
||||
value={dashboardData.topData?.technology_innovation_based_revenue_growth?.replaceAll("," , "") || "0"}
|
||||
percentValue={dashboardData.topData?.technology_innovation_based_revenue_growth_percent}
|
||||
value={
|
||||
dashboardData.topData?.technology_innovation_based_revenue_growth?.replaceAll(
|
||||
",",
|
||||
""
|
||||
) || "0"
|
||||
}
|
||||
percentValue={
|
||||
dashboardData.topData
|
||||
?.technology_innovation_based_revenue_growth_percent
|
||||
}
|
||||
percentLabel="درصد به کل درآمد"
|
||||
/>
|
||||
|
||||
{/* Cost Reduction Card */}
|
||||
<MetricCard
|
||||
title="کاهش هزینه ها مبتنی بر فناوری و نوآوری"
|
||||
value={Math.round(parseFloat(dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(/,/g, "") || "0"))}
|
||||
percentValue={dashboardData.topData?.technology_innovation_based_cost_reduction_percent || "0"}
|
||||
value={Math.round(
|
||||
parseFloat(
|
||||
dashboardData.topData?.technology_innovation_based_cost_reduction?.replace(
|
||||
/,/g,
|
||||
""
|
||||
) || "0"
|
||||
)
|
||||
)}
|
||||
percentValue={
|
||||
dashboardData.topData
|
||||
?.technology_innovation_based_cost_reduction_percent || "0"
|
||||
}
|
||||
percentLabel="درصد به کل هزینه"
|
||||
/>
|
||||
|
||||
|
|
@ -486,7 +512,7 @@ export function DashboardHome() {
|
|||
browser: "budget",
|
||||
visitors: parseFloat(
|
||||
dashboardData.topData
|
||||
?.innovation_budget_achievement_percent || "0",
|
||||
?.innovation_budget_achievement_percent || "0"
|
||||
),
|
||||
fill: "var(--color-green)",
|
||||
},
|
||||
|
|
@ -509,11 +535,7 @@ export function DashboardHome() {
|
|||
className="first:fill-pr-red last:fill-[#24273A]"
|
||||
polarRadius={[38, 31]}
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="visitors"
|
||||
background
|
||||
cornerRadius={5}
|
||||
/>
|
||||
<RadialBar dataKey="visitors" background cornerRadius={5} />
|
||||
<PolarRadiusAxis
|
||||
tick={false}
|
||||
tickLine={false}
|
||||
|
|
@ -539,8 +561,8 @@ export function DashboardHome() {
|
|||
Math.round(
|
||||
dashboardData.topData
|
||||
?.innovation_budget_achievement_percent ||
|
||||
0,
|
||||
),
|
||||
0
|
||||
)
|
||||
)}
|
||||
</tspan>
|
||||
</text>
|
||||
|
|
@ -560,10 +582,10 @@ export function DashboardHome() {
|
|||
parseFloat(
|
||||
dashboardData.topData?.approved_innovation_budget_achievement_ratio?.replace(
|
||||
/,/g,
|
||||
"",
|
||||
) || "0",
|
||||
),
|
||||
),
|
||||
""
|
||||
) || "0"
|
||||
)
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-base font-bold mr-auto">
|
||||
|
|
@ -573,10 +595,10 @@ export function DashboardHome() {
|
|||
parseFloat(
|
||||
dashboardData.topData?.allocated_innovation_budget_achievement_ratio?.replace(
|
||||
/,/g,
|
||||
"",
|
||||
) || "0",
|
||||
),
|
||||
),
|
||||
""
|
||||
) || "0"
|
||||
)
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -598,7 +620,10 @@ export function DashboardHome() {
|
|||
<TabsTrigger value="canvas" className="cursor-pointer">
|
||||
شماتیک
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="charts" className=" text-white cursor-pointer font-light ">
|
||||
<TabsTrigger
|
||||
value="charts"
|
||||
className=" text-white cursor-pointer font-light "
|
||||
>
|
||||
مقایسه ای
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
|
@ -611,14 +636,13 @@ export function DashboardHome() {
|
|||
<TabsContent value="canvas" className="w-ful h-full">
|
||||
<div className="p-4 h-full w-full">
|
||||
<D3ImageInfo
|
||||
companies={
|
||||
companyChartData.map((item) => {
|
||||
companies={companyChartData.map((item) => {
|
||||
const imageMap: Record<string, string> = {
|
||||
"بسپاران": "/besparan.png",
|
||||
"خوارزمی": "/khwarazmi.png",
|
||||
بسپاران: "/besparan.png",
|
||||
خوارزمی: "/khwarazmi.png",
|
||||
"فراورش 1": "/faravash1.png",
|
||||
"فراورش 2": "/faravash2.png",
|
||||
"کیمیا": "/kimia.png",
|
||||
کیمیا: "/kimia.png",
|
||||
"آب نیرو": "/abniro.png",
|
||||
};
|
||||
|
||||
|
|
@ -630,8 +654,7 @@ export function DashboardHome() {
|
|||
capacity: item?.capacityI || 0,
|
||||
revenue: item?.revenueI || 0,
|
||||
};
|
||||
})
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
|
@ -649,7 +672,7 @@ export function DashboardHome() {
|
|||
|
||||
<Progress
|
||||
value={parseFloat(
|
||||
dashboardData.leftData?.technology_intensity,
|
||||
dashboardData.leftData?.technology_intensity
|
||||
)}
|
||||
className="h-4 flex-1"
|
||||
/>
|
||||
|
|
@ -667,21 +690,21 @@ export function DashboardHome() {
|
|||
{
|
||||
label: "اجرا شده",
|
||||
value: parseFloat(
|
||||
dashboardData?.leftData?.executed_project || "0",
|
||||
dashboardData?.leftData?.executed_project || "0"
|
||||
),
|
||||
color: "bg-pr-green",
|
||||
},
|
||||
{
|
||||
label: "در حال اجرا",
|
||||
value: parseFloat(
|
||||
dashboardData?.leftData?.in_progress_project || "0",
|
||||
dashboardData?.leftData?.in_progress_project || "0"
|
||||
),
|
||||
color: "bg-pr-blue",
|
||||
},
|
||||
{
|
||||
label: "برنامهریزی شده",
|
||||
value: parseFloat(
|
||||
dashboardData?.leftData?.planned_project || "0",
|
||||
dashboardData?.leftData?.planned_project || "0"
|
||||
),
|
||||
color: "bg-pr-red",
|
||||
},
|
||||
|
|
@ -706,7 +729,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.printed_books_count || "0",
|
||||
dashboardData.leftData?.printed_books_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -717,7 +740,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.registered_patents_count || "0",
|
||||
dashboardData.leftData?.registered_patents_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -728,7 +751,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.published_reports_count || "0",
|
||||
dashboardData.leftData?.published_reports_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -739,7 +762,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.printed_articles_count || "0",
|
||||
dashboardData.leftData?.printed_articles_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -763,7 +786,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.attended_conferences_count || "0",
|
||||
dashboardData.leftData?.attended_conferences_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -774,7 +797,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.attended_events_count || "0",
|
||||
dashboardData.leftData?.attended_events_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -785,7 +808,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.attended_exhibitions_count || "0",
|
||||
dashboardData.leftData?.attended_exhibitions_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -796,7 +819,7 @@ export function DashboardHome() {
|
|||
</div>
|
||||
<span className="text-base font-bold ">
|
||||
{formatNumber(
|
||||
dashboardData.leftData?.organized_events_count || "0",
|
||||
dashboardData.leftData?.organized_events_count || "0"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -806,7 +829,6 @@ export function DashboardHome() {
|
|||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useAuth } from "~/contexts/auth-context";
|
||||
import { Link } from "react-router";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import jalaali from "jalaali-js";
|
||||
import {
|
||||
Calendar,
|
||||
ChevronLeft,
|
||||
Menu,
|
||||
PanelLeft,
|
||||
|
||||
Server,
|
||||
Settings,
|
||||
User,
|
||||
|
||||
Menu,
|
||||
ChevronDown,
|
||||
Server,
|
||||
ChevronLeft ,
|
||||
|
||||
} from "lucide-react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Calendar as CustomCalendar } from "~/components/ui/Calendar";
|
||||
import { useAuth } from "~/contexts/auth-context";
|
||||
import apiService from "~/lib/api";
|
||||
import { cn, EventBus } from "~/lib/utils";
|
||||
|
||||
interface HeaderProps {
|
||||
onToggleSidebar?: () => void;
|
||||
|
|
@ -24,6 +23,52 @@ interface HeaderProps {
|
|||
titleIcon?: React.ComponentType<{ className?: string }> | null;
|
||||
}
|
||||
|
||||
interface MonthItem {
|
||||
id: string;
|
||||
label: string;
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
|
||||
interface CurrentDay {
|
||||
start?: string;
|
||||
end?: string;
|
||||
sinceMonth?: string;
|
||||
fromMonth?: string;
|
||||
}
|
||||
|
||||
interface SelectedDate {
|
||||
since?: number;
|
||||
until?: number;
|
||||
}
|
||||
|
||||
const monthList: Array<MonthItem> = [
|
||||
{
|
||||
id: "month-1",
|
||||
label: "بهار",
|
||||
start: "01/01",
|
||||
end: "03/31",
|
||||
},
|
||||
{
|
||||
id: "month-2",
|
||||
label: "تابستان",
|
||||
start: "04/01",
|
||||
end: "06/31",
|
||||
},
|
||||
{
|
||||
id: "month-3",
|
||||
label: "پاییز",
|
||||
start: "07/01",
|
||||
end: "09/31",
|
||||
},
|
||||
{
|
||||
id: "month-4",
|
||||
label: "زمستان",
|
||||
start: "10/01",
|
||||
end: "12/29",
|
||||
},
|
||||
];
|
||||
|
||||
export function Header({
|
||||
onToggleSidebar,
|
||||
className,
|
||||
|
|
@ -31,25 +76,111 @@ export function Header({
|
|||
titleIcon,
|
||||
}: HeaderProps) {
|
||||
const { user } = useAuth();
|
||||
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
|
||||
const [isNotificationOpen, setIsNotificationOpen] = useState(false);
|
||||
const { jy } = jalaali.toJalaali(new Date());
|
||||
|
||||
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState<boolean>(false);
|
||||
const [isNotificationOpen, setIsNotificationOpen] = useState<boolean>(false);
|
||||
const [selectedDate, setSelectedDate] = useState<CurrentDay>();
|
||||
const [openCalendar, setOpenCalendar] = useState<boolean>(false);
|
||||
const calendarRef = useRef<HTMLDivElement>(null);
|
||||
const [currentYear, setCurrentYear] = useState<SelectedDate>({
|
||||
since: jy,
|
||||
until: jy,
|
||||
});
|
||||
|
||||
const redirectHandler = async () => {
|
||||
try {
|
||||
const getData = await apiService.post('/GenerateSsoCode')
|
||||
//const url = `http://localhost:3000/redirect/${getData.data}`;
|
||||
const getData = await apiService.post("/GenerateSsoCode");
|
||||
const url = `https://inogen-bpms.pelekan.org/redirect/${getData.data}`;
|
||||
window.open(url, "_blank");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const nextFromYearHandler = () => {
|
||||
if (currentYear && (currentYear.since ?? 0) < (currentYear.until ?? 0)) {
|
||||
setCurrentYear((prev) => ({
|
||||
...prev,
|
||||
since: currentYear?.since! + 1,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const prevFromYearHandler = () => {
|
||||
setCurrentYear((prev) => ({
|
||||
...prev,
|
||||
since: currentYear?.since! - 1,
|
||||
}));
|
||||
};
|
||||
|
||||
const selectFromDateHandler = (val: MonthItem) => {
|
||||
const data = {
|
||||
start: `${currentYear.since}/${val.start}`,
|
||||
sinceMonth: val.label,
|
||||
};
|
||||
setSelectedDate((prev) => ({
|
||||
...prev,
|
||||
...data,
|
||||
}));
|
||||
};
|
||||
|
||||
const nextUntilYearHandler = () => {
|
||||
setCurrentYear((prev) => ({
|
||||
...prev,
|
||||
until: currentYear?.until! + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
const prevUntilYearHandler = () => {
|
||||
if (currentYear && (currentYear.since ?? 0) < (currentYear.until ?? 0)) {
|
||||
setCurrentYear((prev) => ({
|
||||
...prev,
|
||||
until: currentYear?.until! - 1,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const selectUntilDateHandler = (val: MonthItem) => {
|
||||
const data = {
|
||||
end: `${currentYear.until}/${val.end}`,
|
||||
fromMonth: val.label,
|
||||
};
|
||||
setSelectedDate((prev) => ({
|
||||
...prev,
|
||||
...data,
|
||||
}));
|
||||
toggleCalendar();
|
||||
};
|
||||
|
||||
const toggleCalendar = () => {
|
||||
setOpenCalendar(!openCalendar);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
calendarRef.current &&
|
||||
!calendarRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpenCalendar(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
EventBus.emit("dateSelected", selectedDate);
|
||||
}, [currentYear, selectedDate]);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={cn(
|
||||
"backdrop-blur-sm border-b border-gray-400/30 h-16 flex items-center justify-between px-4 lg:px-6 shadow-sm relative z-30",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Left Section */}
|
||||
|
|
@ -77,16 +208,66 @@ export function Header({
|
|||
<PanelLeft />
|
||||
)}
|
||||
{title.includes("-") ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<div className="flex row items-center gap-4">
|
||||
<div className="flex items-center gap-1">
|
||||
{title.split("-")[0]}
|
||||
<ChevronLeft className="inline-block w-4 h-4" />
|
||||
{title.split("-")[1]}
|
||||
</span>
|
||||
) : (
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
|
||||
)}
|
||||
</h1>
|
||||
|
||||
<div ref={calendarRef} className="flex flex-col gap-3 relative">
|
||||
<div
|
||||
onClick={toggleCalendar}
|
||||
className="flex flex-row gap-2 items-center border border-pr-gray p-1.5 rounded-md px-2.5 min-w-72 cursor-pointer hover:bg-pr-gray/50 transition-all duration-300"
|
||||
>
|
||||
<Calendar size={20} />
|
||||
{selectedDate ? (
|
||||
<div className="flex flex-row justify-between w-full gap-2.5 min-w-64 font-bold">
|
||||
<div className="flex flex-row gap-1.5 w-max">
|
||||
<span className="text-md">از</span>
|
||||
<span className="text-md">{selectedDate?.sinceMonth}</span>
|
||||
<span className="text-md">{currentYear.since}</span>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1.5 w-max">
|
||||
<span className="text-md">تا</span>
|
||||
<span className="text-md">{selectedDate?.fromMonth}</span>
|
||||
<span className="text-md">{currentYear.until}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
"تاریخ مورد نظر خود را انتخاب نمایید"
|
||||
)}
|
||||
</div>
|
||||
|
||||
{openCalendar && (
|
||||
<div className="flex flex-row gap-1 absolute top-14 w-full rounded-3xl overflow-hidden bg-pr-gray border-2 border-[#5F6284]">
|
||||
<CustomCalendar
|
||||
title="از"
|
||||
nextYearHandler={nextFromYearHandler}
|
||||
prevYearHandler={prevFromYearHandler}
|
||||
currentYear={currentYear?.since}
|
||||
monthList={monthList}
|
||||
selectedDate={selectedDate?.sinceMonth}
|
||||
selectDateHandler={selectFromDateHandler}
|
||||
/>
|
||||
<span className="w-0.5 h-52 border border-[#5F6284] block mt-3"></span>
|
||||
<CustomCalendar
|
||||
title="تا"
|
||||
nextYearHandler={nextUntilYearHandler}
|
||||
prevYearHandler={prevUntilYearHandler}
|
||||
currentYear={currentYear?.until}
|
||||
monthList={monthList}
|
||||
selectedDate={selectedDate?.fromMonth}
|
||||
selectDateHandler={selectUntilDateHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Section */}
|
||||
|
|
@ -94,14 +275,15 @@ export function Header({
|
|||
{/* User Menu */}
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
{
|
||||
user?.id === 2041 && <button
|
||||
{user?.id === 2041 && (
|
||||
<button
|
||||
className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
|
||||
onClick={redirectHandler}>
|
||||
onClick={redirectHandler}
|
||||
>
|
||||
<Server className="h-4 w-4" />
|
||||
ورود به میزکار مدیریت</button>
|
||||
}
|
||||
ورود به میزکار مدیریت
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -109,7 +291,6 @@ export function Header({
|
|||
onClick={() => setIsProfileMenuOpen(!isProfileMenuOpen)}
|
||||
className="flex items-center gap-2 text-gray-300"
|
||||
>
|
||||
|
||||
<div className="hidden sm:block text-right">
|
||||
<div className="text-sm font-medium font-persian">
|
||||
{user?.name} {user?.family}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { useState } from "react";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { Header } from "./header";
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { StrategicAlignmentPopup } from "./strategic-alignment-popup";
|
||||
import apiService from "~/lib/api";
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -18,9 +17,14 @@ export function DashboardLayout({
|
|||
}: DashboardLayoutProps) {
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
|
||||
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false);
|
||||
const [isStrategicAlignmentPopupOpen, setIsStrategicAlignmentPopupOpen] = useState(false);
|
||||
const [currentTitle, setCurrentTitle] = useState<string | undefined>(title ?? "صفحه اول");
|
||||
const [currentTitleIcon, setCurrentTitleIcon] = useState<React.ComponentType<{ className?: string }> | null | undefined>(undefined);
|
||||
const [isStrategicAlignmentPopupOpen, setIsStrategicAlignmentPopupOpen] =
|
||||
useState(false);
|
||||
const [currentTitle, setCurrentTitle] = useState<string | undefined>(
|
||||
title ?? "صفحه اول"
|
||||
);
|
||||
const [currentTitleIcon, setCurrentTitleIcon] = useState<
|
||||
React.ComponentType<{ className?: string }> | null | undefined
|
||||
>(undefined);
|
||||
|
||||
const toggleSidebarCollapse = () => {
|
||||
setIsSidebarCollapsed(!isSidebarCollapsed);
|
||||
|
|
@ -30,8 +34,6 @@ export function DashboardLayout({
|
|||
setIsMobileSidebarOpen(!isMobileSidebarOpen);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-screen flex overflow-hidden bg-[linear-gradient(to_bottom_left,#464861,20%,#111628)] relative overflow-x-hidden"
|
||||
|
|
@ -55,19 +57,20 @@ export function DashboardLayout({
|
|||
"fixed inset-y-0 right-0 z-50 flex flex-col lg:static lg:inset-auto lg:translate-x-0 transition-transform duration-300 ease-in-out",
|
||||
isMobileSidebarOpen
|
||||
? "translate-x-0"
|
||||
: "translate-x-full lg:translate-x-0",
|
||||
: "translate-x-full lg:translate-x-0"
|
||||
)}
|
||||
>
|
||||
<Sidebar
|
||||
isCollapsed={isSidebarCollapsed}
|
||||
onToggleCollapse={toggleSidebarCollapse}
|
||||
className="h-full flex-shrink-0 relative z-10"
|
||||
onStrategicAlignmentClick={() => setIsStrategicAlignmentPopupOpen(true)}
|
||||
onStrategicAlignmentClick={() =>
|
||||
setIsStrategicAlignmentPopupOpen(true)
|
||||
}
|
||||
onTitleChange={(info) => {
|
||||
setCurrentTitle(info.title);
|
||||
setCurrentTitleIcon(info.icon ?? null);
|
||||
}}
|
||||
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -85,7 +88,7 @@ export function DashboardLayout({
|
|||
<main
|
||||
className={cn(
|
||||
"flex-1 overflow-x-hidden overflow-y-auto focus:outline-none transition-all duration-300 min-w-0",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="relative h-full min-w-0 w-full z-10 overflow-x-hidden p-5">
|
||||
|
|
@ -93,7 +96,10 @@ export function DashboardLayout({
|
|||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<StrategicAlignmentPopup open={isStrategicAlignmentPopupOpen} onOpenChange={setIsStrategicAlignmentPopupOpen} />
|
||||
<StrategicAlignmentPopup
|
||||
open={isStrategicAlignmentPopupOpen}
|
||||
onOpenChange={setIsStrategicAlignmentPopupOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -686,6 +686,12 @@ export function GreenInnovationPage() {
|
|||
{ name: recycleParams.food.label, pv: 30, amt: 50 },
|
||||
]);
|
||||
|
||||
// useEffect(() => {
|
||||
// EventBus.on("dateSelected", (date) => {
|
||||
// debugger;
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<DashboardLayout title="نوآوری سبز">
|
||||
<div className="space-y-4 h-[23.5rem]">
|
||||
|
|
|
|||
|
|
@ -1,46 +1,37 @@
|
|||
import {
|
||||
ArrowDownCircle,
|
||||
ArrowUpCircle,
|
||||
Building2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
CirclePause,
|
||||
DollarSign,
|
||||
Funnel,
|
||||
Loader2,
|
||||
PickaxeIcon,
|
||||
RefreshCw,
|
||||
TrendingUp,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Bar, BarChart, LabelList } from "recharts";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { MetricCard } from "~/components/ui/metric-card";
|
||||
import { BaseCard } from "~/components/ui/base-card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import { Bar, BarChart, LabelList } from "recharts"
|
||||
import { MetricCard } from "~/components/ui/metric-card";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "~/components/ui/popover"
|
||||
PopoverTrigger,
|
||||
} from "~/components/ui/popover";
|
||||
|
||||
import { FunnelChart } from "~/components/ui/funnel-chart";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
|
||||
import {
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { FunnelChart } from "~/components/ui/funnel-chart";
|
||||
import { Skeleton } from "~/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -49,12 +40,10 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} 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 { DashboardLayout } from "../layout";
|
||||
import { Skeleton } from "~/components/ui/skeleton";
|
||||
import { Tooltip as TooltipSh, TooltipTrigger, TooltipContent } from "~/components/ui/tooltip";
|
||||
|
||||
|
||||
interface ProjectData {
|
||||
project_no: string;
|
||||
|
|
@ -139,15 +128,16 @@ const columns = [
|
|||
{ key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" },
|
||||
];
|
||||
|
||||
|
||||
export default function Timeline( valueTimeLine : string) {
|
||||
export default function Timeline(valueTimeLine: string) {
|
||||
const stages = ["تجاری سازی", "توسعه", "تحلیل بازار", "ثبت ایده"];
|
||||
const currentStage = stages?.toReversed()?.findIndex((x : string) => x == valueTimeLine)
|
||||
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
|
||||
}
|
||||
const main = stages?.findIndex((x) => x == "ثبت ایده");
|
||||
console.log("yay ", 25 * main + 12.5);
|
||||
return 25 * main + 12.5;
|
||||
};
|
||||
return (
|
||||
<div className="w-full p-4">
|
||||
{/* Year labels */}
|
||||
|
|
@ -160,12 +150,17 @@ export default function Timeline( valueTimeLine : string) {
|
|||
{/* Timeline bar */}
|
||||
<div className="relative rounded-lg flex mb-4 items-center">
|
||||
{stages.map((stage, index) => (
|
||||
<div key={stage} className="flex-1 flex flex-col items-center relative">
|
||||
<div
|
||||
key={stage}
|
||||
className="flex-1 flex flex-col items-center relative"
|
||||
>
|
||||
<TooltipSh>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={`w-full py-2 text-center transition-colors duration-300 ${
|
||||
index <= currentStage ? "bg-[#3D7968] text-white" : "bg-[#3AEA83] text-slate-600"
|
||||
index <= currentStage
|
||||
? "bg-[#3D7968] text-white"
|
||||
: "bg-[#3AEA83] text-slate-600"
|
||||
}`}
|
||||
>
|
||||
<span className="mt-1 text-sm">{stage}</span>
|
||||
|
|
@ -176,23 +171,30 @@ export default function Timeline( valueTimeLine : string) {
|
|||
))}
|
||||
|
||||
{/* Vertical line showing current position */}
|
||||
{ valueTimeLine?.length > 0 && ( <> <div
|
||||
{valueTimeLine?.length > 0 && (
|
||||
<>
|
||||
{" "}
|
||||
<div
|
||||
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
|
||||
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)}%` }}
|
||||
>وضعیت فعلی</div>
|
||||
</> ) }
|
||||
|
||||
style={{
|
||||
left: `${(currentStage + 0.5) * (100 / stages.length)}%`,
|
||||
}}
|
||||
>
|
||||
وضعیت فعلی
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function ProductInnovationPage() {
|
||||
const [showPopup, setShowPopup] = useState(false);
|
||||
const [projects, setProjects] = useState<ProductInnovationData[]>([]);
|
||||
|
|
@ -241,7 +243,7 @@ export function ProductInnovationPage() {
|
|||
description: "میلیون ریال",
|
||||
descriptionPercent: "درصد به کل درآمد",
|
||||
color: "text-[#3AEA83]",
|
||||
percent :0
|
||||
percent: 0,
|
||||
},
|
||||
newProductExports: {
|
||||
id: "newProductExports",
|
||||
|
|
@ -262,11 +264,9 @@ export function ProductInnovationPage() {
|
|||
const observerRef = useRef<HTMLDivElement>(null);
|
||||
const fetchingRef = useRef(false);
|
||||
|
||||
|
||||
|
||||
const handleProjectDetails = async (project: ProductInnovationData) => {
|
||||
setSelectedProjectDetails(project);
|
||||
console.log(project)
|
||||
console.log(project);
|
||||
setDetailsDialogOpen(true);
|
||||
await fetchPopupData(project);
|
||||
};
|
||||
|
|
@ -278,26 +278,27 @@ export function ProductInnovationPage() {
|
|||
// Fetch popup stats
|
||||
const statsResponse = await apiService.call({
|
||||
innovation_product_popup_function1: {
|
||||
project_id: project.project_id
|
||||
}
|
||||
project_id: project.project_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (statsResponse.state === 0) {
|
||||
const statsData = JSON.parse(statsResponse.data);
|
||||
if (statsData.innovation_product_popup_function1 && statsData.innovation_product_popup_function1[0]) {
|
||||
setPopupStats(JSON.parse(statsData.innovation_product_popup_function1)[0]);
|
||||
if (
|
||||
statsData.innovation_product_popup_function1 &&
|
||||
statsData.innovation_product_popup_function1[0]
|
||||
) {
|
||||
setPopupStats(
|
||||
JSON.parse(statsData.innovation_product_popup_function1)[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch export chart data
|
||||
const chartResponse = await apiService.select({
|
||||
ProcessName: "export_product_innovation",
|
||||
OutputFields: [
|
||||
"product_title",
|
||||
"full_season",
|
||||
"sum(export_revenue)"
|
||||
],
|
||||
GroupBy: ["product_title", "full_season"]
|
||||
OutputFields: ["product_title", "full_season", "sum(export_revenue)"],
|
||||
GroupBy: ["product_title", "full_season"],
|
||||
});
|
||||
if (chartResponse.state === 0) {
|
||||
const chartData = JSON.parse(chartResponse.data);
|
||||
|
|
@ -305,14 +306,13 @@ export function ProductInnovationPage() {
|
|||
// Set all data for line chart
|
||||
|
||||
// Filter data for the selected project (bar chart)
|
||||
const filteredData = chartData.filter(item =>
|
||||
item.product_title === project?.title
|
||||
const filteredData = chartData.filter(
|
||||
(item) => item.product_title === project?.title
|
||||
);
|
||||
setAllExportData(chartData);
|
||||
setExportChartData(filteredData);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching popup data:", error);
|
||||
} finally {
|
||||
|
|
@ -358,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", "=", "نوآوری در محصول"]],
|
||||
|
|
@ -424,11 +424,15 @@ export function ProductInnovationPage() {
|
|||
}
|
||||
};
|
||||
|
||||
const fetchStats = async () => {
|
||||
const fetchStats = async (startDate?: string, endDate?: string) => {
|
||||
try {
|
||||
setStatsLoading(true);
|
||||
|
||||
const raw = await apiService.call<any>({
|
||||
innovation_product_function: {},
|
||||
innovation_product_function: {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
},
|
||||
});
|
||||
|
||||
let payload: any = JSON.parse(raw?.data);
|
||||
|
|
@ -444,21 +448,25 @@ export function ProductInnovationPage() {
|
|||
return 0;
|
||||
};
|
||||
|
||||
const data: Array<any> = JSON.parse(
|
||||
payload?.innovation_product_function
|
||||
);
|
||||
const data: Array<any> = JSON.parse(payload?.innovation_product_function);
|
||||
const stats = data[0];
|
||||
const normalized: ProductInnovationStats = {
|
||||
new_products_revenue_share: parseNum(stats?.new_products_revenue_share),
|
||||
new_products_revenue_share_percent: parseNum(stats?.new_products_revenue_share_percent),
|
||||
new_products_revenue_share_percent: parseNum(
|
||||
stats?.new_products_revenue_share_percent
|
||||
),
|
||||
import_impact: parseNum(stats?.import_impact),
|
||||
new_products_export: parseNum(stats?.new_products_export),
|
||||
all_funnel: parseNum(stats?.all_funnel),
|
||||
successful_sample_funnel: parseNum(stats?.successful_sample_funnel),
|
||||
successful_products_funnel: parseNum(stats?.successful_products_funnel),
|
||||
successful_improvement_or_change_funnel: parseNum(stats?.successful_improvement_or_change_funnel),
|
||||
successful_improvement_or_change_funnel: parseNum(
|
||||
stats?.successful_improvement_or_change_funnel
|
||||
),
|
||||
new_product_funnel: parseNum(stats?.new_product_funnel),
|
||||
count_innovation_construction_inside_projects: parseNum(stats?.count_innovation_construction_inside_projects),
|
||||
count_innovation_construction_inside_projects: parseNum(
|
||||
stats?.count_innovation_construction_inside_projects
|
||||
),
|
||||
average_project_score: parseNum(stats?.average_project_score),
|
||||
};
|
||||
|
||||
|
|
@ -538,7 +546,6 @@ export function ProductInnovationPage() {
|
|||
setHasMore(true);
|
||||
};
|
||||
|
||||
|
||||
const formatCurrency = (amount: string | number) => {
|
||||
if (!amount) return "0 ریال";
|
||||
const numericAmount =
|
||||
|
|
@ -551,15 +558,19 @@ export function ProductInnovationPage() {
|
|||
|
||||
// Transform data for line chart
|
||||
const transformDataForLineChart = (data: any[]) => {
|
||||
const seasons = [...new Set(data.map(item => item.full_season))];
|
||||
const products = [...new Set(data.map(item => item.product_title))];
|
||||
return seasons.map(season => {
|
||||
const seasons = [...new Set(data.map((item) => item.full_season))];
|
||||
const products = [...new Set(data.map((item) => item.product_title))];
|
||||
return seasons.map((season) => {
|
||||
const seasonData: any = { season };
|
||||
products.forEach(product => {
|
||||
const productData = data.find(item =>
|
||||
products.forEach((product) => {
|
||||
const productData = data.find(
|
||||
(item) =>
|
||||
item.product_title === product && item.full_season === season
|
||||
);
|
||||
seasonData[product] = productData?.export_revenue_sum > 0 && productData ? Math.round(productData?.export_revenue_sum) : 0;
|
||||
seasonData[product] =
|
||||
productData?.export_revenue_sum > 0 && productData
|
||||
? Math.round(productData?.export_revenue_sum)
|
||||
: 0;
|
||||
});
|
||||
return seasonData;
|
||||
});
|
||||
|
|
@ -615,7 +626,8 @@ export function ProductInnovationPage() {
|
|||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
handleProjectDetails(item)}}
|
||||
handleProjectDetails(item);
|
||||
}}
|
||||
className="text-emerald-400 underline underline-offset-4 font-ligth text-sm p-2 h-auto"
|
||||
>
|
||||
جزئیات بیشتر
|
||||
|
|
@ -628,7 +640,9 @@ export function ProductInnovationPage() {
|
|||
</Badge>
|
||||
);
|
||||
case "title":
|
||||
return <span className="font-light text-sm text-white">{String(value)}</span>;
|
||||
return (
|
||||
<span className="font-light text-sm text-white">{String(value)}</span>
|
||||
);
|
||||
case "project_status":
|
||||
return (
|
||||
<div className="flex items-center text-sm font-light gap-1">
|
||||
|
|
@ -652,7 +666,11 @@ export function ProductInnovationPage() {
|
|||
</Badge>
|
||||
);
|
||||
default:
|
||||
return <span className="text-white text-sm font-light">{String(value) || "-"}</span>;
|
||||
return (
|
||||
<span className="text-white text-sm font-light">
|
||||
{String(value) || "-"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -667,7 +685,8 @@ export function ProductInnovationPage() {
|
|||
})
|
||||
.map((item) => ({
|
||||
label: item.full_season,
|
||||
value: item.export_revenue_sum < 0 ? 0 : Math.round(item.export_revenue_sum) ,
|
||||
value:
|
||||
item.export_revenue_sum < 0 ? 0 : Math.round(item.export_revenue_sum),
|
||||
}));
|
||||
|
||||
return (
|
||||
|
|
@ -719,18 +738,27 @@ export function ProductInnovationPage() {
|
|||
value={stateCard.revenueNewProducts.value}
|
||||
percentValue={stateCard.revenueNewProducts.percent}
|
||||
valueLabel={stateCard.revenueNewProducts.description}
|
||||
percentLabel={stateCard.revenueNewProducts.descriptionPercent}
|
||||
percentLabel={
|
||||
stateCard.revenueNewProducts.descriptionPercent
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Second card */}
|
||||
<div>
|
||||
<BaseCard title={stateCard.newProductExports.title} className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<BaseCard
|
||||
title={stateCard.newProductExports.title}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
|
||||
>
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-3xl font-bold mb-1 text-pr-green">{stateCard.newProductExports.value}</p>
|
||||
<div className="text-xs text-gray-400 font-persian">{stateCard.newProductExports.description}</div>
|
||||
<p className="text-3xl font-bold mb-1 text-pr-green">
|
||||
{stateCard.newProductExports.value}
|
||||
</p>
|
||||
<div className="text-xs text-gray-400 font-persian">
|
||||
{stateCard.newProductExports.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -739,12 +767,19 @@ export function ProductInnovationPage() {
|
|||
|
||||
{/* Third card - basic BaseCard */}
|
||||
<div>
|
||||
<BaseCard title={stateCard.impactOnImports.title} className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50">
|
||||
<BaseCard
|
||||
title={stateCard.impactOnImports.title}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
|
||||
>
|
||||
<div className="flex items-center justify-center flex-col">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-center">
|
||||
<p className="text-3xl font-bold mb-1 text-pr-red">{stateCard.impactOnImports.value}</p>
|
||||
<div className="text-xs text-gray-400 font-persian">{stateCard.impactOnImports.description}</div>
|
||||
<p className="text-3xl font-bold mb-1 text-pr-red">
|
||||
{stateCard.impactOnImports.value}
|
||||
</p>
|
||||
<div className="text-xs text-gray-400 font-persian">
|
||||
{stateCard.impactOnImports.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -902,7 +937,10 @@ export function ProductInnovationPage() {
|
|||
<div className="flex gap-4 text-sm text-gray-300 font-persian justify-between sm:flex-col xl:flex-row">
|
||||
<div className="text-center gap-2 items-center xl:w-1/3 pr-36 sm:w-full">
|
||||
<div className="text-sm font-semibold text-white">
|
||||
کل پروژه ها :{formatNumber(stats?.count_innovation_construction_inside_projects)}
|
||||
کل پروژه ها :
|
||||
{formatNumber(
|
||||
stats?.count_innovation_construction_inside_projects
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -917,7 +955,9 @@ export function ProductInnovationPage() {
|
|||
<div className="text-bold text-sm text-white">میانگین :</div>
|
||||
<div className="font-bold text-sm text-white">
|
||||
{formatNumber(
|
||||
((stats.average_project_score ?? 0) as number).toFixed?.(1) ?? 0
|
||||
((stats.average_project_score ?? 0) as number).toFixed?.(
|
||||
1
|
||||
) ?? 0
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -941,29 +981,47 @@ export function ProductInnovationPage() {
|
|||
<div className="space-y-4">
|
||||
{/* Stats Cards */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-bold text-base">{selectedProjectDetails?.title}</h3>
|
||||
<p className="py-2">{selectedProjectDetails?.project_description}</p>
|
||||
<h3 className="font-bold text-base">
|
||||
{selectedProjectDetails?.title}
|
||||
</h3>
|
||||
<p className="py-2">
|
||||
{selectedProjectDetails?.project_description}
|
||||
</p>
|
||||
</div>
|
||||
<Timeline valueTimeLine={selectedProjectDetails?.current_status} />
|
||||
<Timeline
|
||||
valueTimeLine={selectedProjectDetails?.current_status}
|
||||
/>
|
||||
|
||||
{/* Technical Knowledge */}
|
||||
<div className=" rounded-lg py-2 mb-0">
|
||||
<h3 className="text-sm text-white font-semibold mb-2">دانش فنی محصول جدید</h3>
|
||||
<h3 className="text-sm text-white font-semibold mb-2">
|
||||
دانش فنی محصول جدید
|
||||
</h3>
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-white font-light">توسعه درونزا</span>
|
||||
<span className="text-sm text-white font-light">
|
||||
توسعه درونزا
|
||||
</span>
|
||||
|
||||
<Checkbox
|
||||
checked={selectedProjectDetails?.developed_technology_type === "توسعه درونزا"}
|
||||
checked={
|
||||
selectedProjectDetails?.developed_technology_type ===
|
||||
"توسعه درونزا"
|
||||
}
|
||||
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-white font-light">همکاری فناورانه</span>
|
||||
<span className="text-sm text-white font-light">
|
||||
همکاری فناورانه
|
||||
</span>
|
||||
|
||||
<Checkbox
|
||||
checked={selectedProjectDetails?.developed_technology_type === "همکاری فناوری"}
|
||||
checked={
|
||||
selectedProjectDetails?.developed_technology_type ===
|
||||
"همکاری فناوری"
|
||||
}
|
||||
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -971,11 +1029,14 @@ export function ProductInnovationPage() {
|
|||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-white font-light">سایر</span>
|
||||
<Checkbox
|
||||
checked={selectedProjectDetails?.developed_technology_type === "سایر"}
|
||||
checked={
|
||||
selectedProjectDetails?.developed_technology_type ===
|
||||
"سایر"
|
||||
}
|
||||
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Standards */}
|
||||
|
|
@ -984,15 +1045,20 @@ export function ProductInnovationPage() {
|
|||
استانداردهای ملی و بینالمللی اخذ شده
|
||||
</h3>
|
||||
|
||||
{selectedProjectDetails?.obtained_standard_title && selectedProjectDetails?.obtained_standard_title.length > 0 ? (
|
||||
{selectedProjectDetails?.obtained_standard_title &&
|
||||
selectedProjectDetails?.obtained_standard_title.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{(Array.isArray(selectedProjectDetails?.obtained_standard_title)
|
||||
{(Array.isArray(
|
||||
selectedProjectDetails?.obtained_standard_title
|
||||
)
|
||||
? selectedProjectDetails?.obtained_standard_title
|
||||
: [selectedProjectDetails?.obtained_standard_title]
|
||||
).map((standard, index) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
|
||||
<span className="text-sm text-white font-light">{standard}</span>
|
||||
<span className="text-sm text-white font-light">
|
||||
{standard}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -1001,11 +1067,12 @@ export function ProductInnovationPage() {
|
|||
هیچ استانداردی ثبت نشده است.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Knowledge-based Certificate Button */}
|
||||
<div className="justify-self-centerr grid py-1 mx-auto">
|
||||
{selectedProjectDetails?.knowledge_based_certificate_obtained === "خیر" ? (
|
||||
{selectedProjectDetails?.knowledge_based_certificate_obtained ===
|
||||
"خیر" ? (
|
||||
<div className=" border border-pr-red mx-auto rounded-lg p-2 text-center">
|
||||
<button className="text-pr-red font-bold text-sm">
|
||||
گواهی دانشبنیان ندارد
|
||||
|
|
@ -1036,10 +1103,14 @@ export function ProductInnovationPage() {
|
|||
</p>
|
||||
<p className="text-sm text-white">
|
||||
<span className="font-bold">تاریخ اخذ: </span>
|
||||
{handleDataValue(selectedProjectDetails?.certificate_obtain_date) || "—"}
|
||||
{handleDataValue(
|
||||
selectedProjectDetails?.certificate_obtain_date
|
||||
) || "—"}
|
||||
</p>
|
||||
<p className="text-sm text-white">
|
||||
<span className="font-bold">مرجع صادرکننده: </span>
|
||||
<span className="font-bold">
|
||||
مرجع صادرکننده:{" "}
|
||||
</span>
|
||||
{selectedProjectDetails?.issuing_authority || "—"}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -1081,16 +1152,32 @@ export function ProductInnovationPage() {
|
|||
<div className="rounded-lg pt-4 grid grid-cols-2 gap-4 w-full">
|
||||
<MetricCard
|
||||
title="میزان صادارت محصول جدید"
|
||||
value={Math.round(popupStats?.new_products_export > 0 ? popupStats?.new_products_export : 0)}
|
||||
percentValue={Math.round(popupStats?.new_products_export_percent > 0 ? popupStats?.new_products_export_percent : 0)}
|
||||
value={Math.round(
|
||||
popupStats?.new_products_export > 0
|
||||
? popupStats?.new_products_export
|
||||
: 0
|
||||
)}
|
||||
percentValue={Math.round(
|
||||
popupStats?.new_products_export_percent > 0
|
||||
? popupStats?.new_products_export_percent
|
||||
: 0
|
||||
)}
|
||||
valueLabel="میلیون ریال"
|
||||
percentLabel="درصد به کل صادرات"
|
||||
/>
|
||||
|
||||
<MetricCard
|
||||
title="تاثیر در واردات"
|
||||
value={Math.round(popupStats?.import_impact > 0 ? popupStats?.import_impact : 0)}
|
||||
percentValue={Math.round(popupStats?.import_impact_percent > 0 ? popupStats?.import_impact_percent : 0)}
|
||||
value={Math.round(
|
||||
popupStats?.import_impact > 0
|
||||
? popupStats?.import_impact
|
||||
: 0
|
||||
)}
|
||||
percentValue={Math.round(
|
||||
popupStats?.import_impact_percent > 0
|
||||
? popupStats?.import_impact_percent
|
||||
: 0
|
||||
)}
|
||||
valueLabel="میلیون ریال"
|
||||
percentLabel="درصد صرفه جویی"
|
||||
/>
|
||||
|
|
@ -1098,7 +1185,9 @@ export function ProductInnovationPage() {
|
|||
|
||||
{/* Export Revenue Bar Chart */}
|
||||
<div className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] rounded-lg px-6 py-4">
|
||||
<h3 className="text-sm font-semibold text-white">ظرفیت صادر شده</h3>
|
||||
<h3 className="text-sm font-semibold text-white">
|
||||
ظرفیت صادر شده
|
||||
</h3>
|
||||
<div className="h-60">
|
||||
{exportChartData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
|
|
@ -1116,52 +1205,149 @@ export function ProductInnovationPage() {
|
|||
axisLine={false}
|
||||
stroke="#C3C3C3"
|
||||
tickMargin={8}
|
||||
tickFormatter={(value: string) => `${value.split(" ")[0]} ${formatNumber(value.split(" ")[1]).replaceAll('٬','')}`}
|
||||
tickFormatter={(value: string) =>
|
||||
`${value.split(" ")[0]} ${formatNumber(value.split(" ")[1]).replaceAll("٬", "")}`
|
||||
}
|
||||
fontSize={11}
|
||||
/>
|
||||
<YAxis tickLine={false} axisLine={false} stroke="#9CA3AF" fontSize={11} tick={{ dx: -50 }} tickFormatter={(value: number) => `${formatNumber(value)} میلیون`} />
|
||||
<YAxis
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
stroke="#9CA3AF"
|
||||
fontSize={11}
|
||||
tick={{ dx: -50 }}
|
||||
tickFormatter={(value: number) =>
|
||||
`${formatNumber(value)} میلیون`
|
||||
}
|
||||
/>
|
||||
<Bar dataKey="value" fill="#10B981" radius={10}>
|
||||
<LabelList formatter={(value: number) => `${formatNumber(value)}`} position="top" offset={15} fill="F9FAFB" className="fill-foreground" fontSize={16} />
|
||||
<LabelList
|
||||
formatter={(value: number) =>
|
||||
`${formatNumber(value)}`
|
||||
}
|
||||
position="top"
|
||||
offset={15}
|
||||
fill="F9FAFB"
|
||||
className="fill-foreground"
|
||||
fontSize={16}
|
||||
/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-gray-400">دادهای برای نمایش وجود ندارد</div>
|
||||
<div className="flex items-center justify-center h-full text-gray-400">
|
||||
دادهای برای نمایش وجود ندارد
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Export Revenue Line Chart */}
|
||||
<div className="bg-[linear-gradient(to_bottom_left,#464861,45%,#111628)] rounded-lg px-6 py-4">
|
||||
<h3 className="text-sm font-semibold text-white">ظرفیت صادر شده</h3>
|
||||
<h3 className="text-sm font-semibold text-white">
|
||||
ظرفیت صادر شده
|
||||
</h3>
|
||||
<div className="h-60">
|
||||
{allExportData.length > 0 ? (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart className="aspect-auto w-full" data={transformDataForLineChart(allExportData)} margin={{ top: 20, right: 30, left: 10, bottom: 50 }}>
|
||||
<LineChart
|
||||
className="aspect-auto w-full"
|
||||
data={transformDataForLineChart(allExportData)}
|
||||
margin={{ top: 20, right: 30, left: 10, bottom: 50 }}
|
||||
>
|
||||
<CartesianGrid vertical={false} stroke="#374151" />
|
||||
<XAxis dataKey="season" stroke="#9CA3AF" fontSize={11} tick={({ x, y, payload }) => (
|
||||
<XAxis
|
||||
dataKey="season"
|
||||
stroke="#9CA3AF"
|
||||
fontSize={11}
|
||||
tick={({ x, y, payload }) => (
|
||||
<g transform={`translate(${x},${y + 10})`}>
|
||||
<text x={-40} y={15} dy={0} textAnchor="end" fill="#9CA3AF" fontSize={11} transform="rotate(-45)">{(payload as any).value}</text>
|
||||
<text
|
||||
x={-40}
|
||||
y={15}
|
||||
dy={0}
|
||||
textAnchor="end"
|
||||
fill="#9CA3AF"
|
||||
fontSize={11}
|
||||
transform="rotate(-45)"
|
||||
>
|
||||
{(payload as any).value}
|
||||
</text>
|
||||
</g>
|
||||
)} />
|
||||
<YAxis tickLine={false} axisLine={false} stroke="#9CA3AF" fontSize={11} tick={{ dx: -50 }} tickFormatter={(value) => `${formatNumber(value)} میلیون`} />
|
||||
<Tooltip formatter={(value: number) => `${formatNumber(value)} میلیون`} contentStyle={{ backgroundColor: "#1F2937", border: "1px solid #374151", borderRadius: "6px", padding: "6px 10px", fontSize: "11px", color: "#F9FAFB" }} />
|
||||
<Legend layout="vertical" verticalAlign="middle" align="right" iconType={"plainline"} className="!flex" wrapperStyle={{ fontSize: 11, paddingLeft: 12, gap: 10 }} />
|
||||
{[...new Set(allExportData.map((item) => item.product_title))].slice(0, 5).map((product, index) => {
|
||||
const colors = ["#10B981", "#EF4444", "#3B82F6", "#F59E0B", "#8B5CF6"];
|
||||
return <Line key={product} type="linear" dot={false} activeDot={{ r: 5 }} dataKey={product} stroke={colors[index % colors.length]} strokeWidth={2} />;
|
||||
)}
|
||||
/>
|
||||
<YAxis
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
stroke="#9CA3AF"
|
||||
fontSize={11}
|
||||
tick={{ dx: -50 }}
|
||||
tickFormatter={(value) =>
|
||||
`${formatNumber(value)} میلیون`
|
||||
}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number) =>
|
||||
`${formatNumber(value)} میلیون`
|
||||
}
|
||||
contentStyle={{
|
||||
backgroundColor: "#1F2937",
|
||||
border: "1px solid #374151",
|
||||
borderRadius: "6px",
|
||||
padding: "6px 10px",
|
||||
fontSize: "11px",
|
||||
color: "#F9FAFB",
|
||||
}}
|
||||
/>
|
||||
<Legend
|
||||
layout="vertical"
|
||||
verticalAlign="middle"
|
||||
align="right"
|
||||
iconType={"plainline"}
|
||||
className="!flex"
|
||||
wrapperStyle={{
|
||||
fontSize: 11,
|
||||
paddingLeft: 12,
|
||||
gap: 10,
|
||||
}}
|
||||
/>
|
||||
{[
|
||||
...new Set(
|
||||
allExportData.map((item) => item.product_title)
|
||||
),
|
||||
]
|
||||
.slice(0, 5)
|
||||
.map((product, index) => {
|
||||
const colors = [
|
||||
"#10B981",
|
||||
"#EF4444",
|
||||
"#3B82F6",
|
||||
"#F59E0B",
|
||||
"#8B5CF6",
|
||||
];
|
||||
return (
|
||||
<Line
|
||||
key={product}
|
||||
type="linear"
|
||||
dot={false}
|
||||
activeDot={{ r: 5 }}
|
||||
dataKey={product}
|
||||
stroke={colors[index % colors.length]}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full text-gray-400">دادهای برای نمایش وجود ندارد</div>
|
||||
<div className="flex items-center justify-center h-full text-gray-400">
|
||||
دادهای برای نمایش وجود ندارد
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
67
app/components/ui/Calendar.tsx
Normal file
67
app/components/ui/Calendar.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
interface MonthItem {
|
||||
id: string;
|
||||
label: string;
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
|
||||
// interface CurrentDay {
|
||||
// start: string;
|
||||
// end: string;
|
||||
// month: string;
|
||||
// }
|
||||
|
||||
interface CalendarProps {
|
||||
title: string;
|
||||
nextYearHandler: () => void;
|
||||
prevYearHandler: () => void;
|
||||
currentYear?: number;
|
||||
monthList: Array<MonthItem>;
|
||||
selectedDate?: string;
|
||||
selectDateHandler: (item: MonthItem) => void;
|
||||
}
|
||||
|
||||
export const Calendar: React.FC<CalendarProps> = ({
|
||||
title,
|
||||
nextYearHandler,
|
||||
prevYearHandler,
|
||||
currentYear,
|
||||
monthList,
|
||||
selectedDate,
|
||||
selectDateHandler,
|
||||
}) => {
|
||||
return (
|
||||
<div className="filter-box bg-pr-gray p-3 w-full">
|
||||
<header className="flex flex-row border-b border-[#5F6284] pb-1.5 justify-center">
|
||||
<span className="font-light">{title}</span>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<ChevronRight
|
||||
className="inline-block w-6 h-6 cursor-pointer"
|
||||
onClick={nextYearHandler}
|
||||
/>
|
||||
<span className="font-light">{currentYear}</span>
|
||||
<ChevronLeft
|
||||
className="inline-block w-6 h-6 cursor-pointer"
|
||||
onClick={prevYearHandler}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<div className="content flex flex-col gap-2 text-center pt-1 cursor-pointer">
|
||||
{monthList.map((item, index) => (
|
||||
<span
|
||||
key={`${item.id}-${index}`}
|
||||
className={`text-lg hover:bg-[#33364D] p-1 rounded-xl transition-all duration-300 ${
|
||||
selectedDate === item.label ? `bg-[#33364D]` : ""
|
||||
}`}
|
||||
onClick={() => selectDateHandler(item)}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import EventEmitter from "events";
|
||||
import moment from "moment-jalaali";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
|
|
@ -22,8 +23,6 @@ export const formatCurrency = (amount: string | number) => {
|
|||
return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال";
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* محاسبه دامنه nice numbers برای محور Y نمودارها
|
||||
* @param values آرایه از مقادیر دادهها
|
||||
|
|
@ -117,7 +116,7 @@ function calculateNiceNumber(value: number, round: boolean): number {
|
|||
}
|
||||
|
||||
export const handleDataValue = (val: any): any => {
|
||||
moment.loadPersian({ usePersianDigits: true });
|
||||
moment.loadPersian({ usePersianDigits: true });
|
||||
if (val == null) return val;
|
||||
if (
|
||||
typeof val === "string" &&
|
||||
|
|
@ -132,4 +131,6 @@ moment.loadPersian({ usePersianDigits: true });
|
|||
return val.toString().replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[+d]);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
export const EventBus = new EventEmitter();
|
||||
|
|
|
|||
6
app/types/util.type.ts
Normal file
6
app/types/util.type.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface CalendarDate {
|
||||
start: string;
|
||||
end: string;
|
||||
sinceMonth: string;
|
||||
untilMonth: string;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user