delete the useless files ,also update the chart and canvas #7

Merged
Saeed0920 merged 1 commits from dashboard_charts into main 2025-09-07 03:39:27 +03:30
4 changed files with 179 additions and 156 deletions

View File

@ -1,5 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import React from "react";
import * as d3 from "d3";
import { formatNumber } from "~/lib/utils"; import { formatNumber } from "~/lib/utils";
export type CompanyInfo = { export type CompanyInfo = {
@ -17,141 +16,162 @@ export type D3ImageInfoProps = {
height?: number; height?: number;
}; };
export function D3ImageInfo({ companies, width = 900, height = 400 }: D3ImageInfoProps) { const InfoBox = ({ company, style }: { company: CompanyInfo; style :any }) => {
const containerRef = useRef<HTMLDivElement | null>(null);
const svgRef = useRef<SVGSVGElement | null>(null);
const draw = () => {
if (!containerRef.current || !svgRef.current) return;
const container = containerRef.current;
const svg = d3.select(svgRef.current);
const W = Math.max(480, container.clientWidth || width);
const H = Math.max(300, height);
svg.attr("width", W).attr("height", H);
svg.selectAll("*").remove();
const padding = 10;
const cols = 3;
const rows = 2;
const boxWidth = (W - padding * (cols + 1)) / cols;
const boxHeight = (H - padding * (rows + 1)) / rows;
const group = svg.append("g").attr("transform", `translate(${padding}, ${padding})`);
companies.forEach((company, i) => {
const col = i % cols;
const row = Math.floor(i / cols);
const x = col * (boxWidth + padding);
const y = row * (boxHeight + padding);
const companyGroup = group.append("g").attr("transform", `translate(${x}, ${y})`);
// Draw background box
companyGroup
.append("rect")
.attr("width", boxWidth)
.attr("height", boxHeight)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill", "transparent")
// Draw image
const imgSize = Math.min(boxWidth, boxHeight) * 0.5;
companyGroup
.append("image")
.attr("href", company.imageUrl)
.attr("x", 10)
.attr("y", 10)
.attr("width", imgSize)
.attr("height", imgSize)
.attr("preserveAspectRatio", "xMidYMid slice")
.style("background", "transparent");
// Adjust positions to match picture
// Position image slightly left and info box to right with spacing
const infoX = imgSize + 30;
const infoY = 10;
const infoWidth = 120;
const infoHeight = imgSize;
const infoGroup = companyGroup.append("g");
infoGroup
.append("rect")
.attr("x", infoX)
.attr("y", infoY)
.attr("width", infoWidth)
.attr("height", infoHeight)
.attr("rx", 10)
.attr("ry", 10)
.attr("fill", "transparent")
.attr("stroke", "#3F415A")
.attr("stroke-width", 1);
// Add text inside info box
const lineHeight = 20;
infoGroup
.append("text")
.attr("x", imgSize + 10 )
.attr("y", infoY + imgSize + 10)
.attr("fill", "#FFFFFF")
.attr("font-weight", "700")
.attr("font-size", 14)
.text(company.name);
infoGroup
.append("text")
.attr("x", infoX + imgSize)
.attr("y", infoY + lineHeight )
.attr("fill", "#FFFFFF")
.attr("font-size", 12)
.text(`درآمد: ${formatNumber(company?.revenue || "0")}`);
infoGroup
.append("text")
.attr("x", infoX + imgSize -20 )
.attr("y", infoY + lineHeight +5 )
.attr("fill", "#ACACAC")
.attr("font-size", 6)
.text(`میلیون ریال`);
infoGroup
.append("text")
.attr("x", infoX + imgSize)
.attr("y", infoY + lineHeight *2 )
.attr("fill", "#FFFFFF")
.attr("font-size", 12)
.text(`هزینه: ${formatNumber(company?.cost || "0")} میلیون ریال`);
infoGroup
.append("text")
.attr("x", infoX + imgSize)
.attr("y", infoY + lineHeight * 3 )
.attr("fill", "#FFFFFF")
.attr("font-size", 12)
.text(`ظرفیت: ${formatNumber(company?.capacity || "0")} تن در سال`);
// Remove click handlers and popup
companyGroup.style("cursor", "default");
});
};
useEffect(() => {
draw();
const ro = new ResizeObserver(() => draw());
if (containerRef.current) ro.observe(containerRef.current);
return () => ro.disconnect();
}, [companies, width, height]);
return ( return (
<div className="w-full h-full"> <div className={`info-box`} style={style}>
<div ref={containerRef} className="w-full h-[400px]"> <div className="info-box-content">
<svg ref={svgRef} className="block w-full h-full"></svg> <div className="info-row">
<div className="info-label">درآمد:</div>
<div className="info-value revenue">{formatNumber(company?.revenue || 0)}</div>
<div className="info-unit">میلیون ریال</div>
</div>
<div className="info-row">
<div className="info-label">هزینه:</div>
<div className="info-value cost">{formatNumber(company?.cost || 0)}</div>
<div className="info-unit">میلیون ریال</div>
</div>
<div className="info-row">
<div className="info-label">ظرفیت:</div>
<div className="info-value capacity">{formatNumber(company?.capacity || 0)}</div>
<div className="info-unit">تن در سال</div>
</div>
</div> </div>
</div> </div>
); );
};
export function D3ImageInfo({ companies }: D3ImageInfoProps) {
// Ensure we have exactly 6 companies
const displayCompanies = companies;
// Positions inside a 5x4 grid (col, row)
// Layout keeps same visual logic: left/middle/right on two bands with spacing grid around
const gridPositions = [
{ col: 2, row: 2 , colI : 1 , rowI : 2 , name : "بسپاران"}, // left - top band
{ col: 3, row: 2 , colI : 3 , rowI : 1 , name : "خوارزمی"}, // middle top (image sits in row 2, info box goes to row 1)
{ col: 4, row: 2 ,colI : 5 , rowI : 2 , name : "فراورش 1"}, // right - top band
{ col: 2, row: 3 , colI : 1 , rowI : 3 , name : "کیمیا"}, // left - bottom band
{ col: 3, row: 3 , colI : 3, rowI : 4 , name : "آب نیرو"}, // middle bottom (image sits in row 3, info box goes to row 4)
{ col: 4, row: 3 , colI : 5 , rowI : 3 , name : "فراورش 2"}, // right - bottom band
];
return (
<div className="w-full h-[500px] rounded-xl p-4">
<div dir="ltr" className="company-grid-container">
{displayCompanies.map((company, index) => {
const gp = gridPositions.find(v => v.name === company.name) || { col: (index % 5) + 1, row: Math.floor(index / 5) + 1 };
return (
<>
<div
key={company.id}
className={`company-item`}
style={{ gridColumn: gp.col, gridRow: gp.row }}
>
<div className="company-image-containe">
<img
src={company.imageUrl}
alt={company.name}
className="company-image"
/>
</div>
{company.name}
</div>
<InfoBox company={company} key={index +10} style={{ gridColumn: gp.colI , gridRow: gp.rowI }} />
</>);
})}
</div>
<style jsx>{`
.company-grid-container {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(4, 1fr);
gap: 5px;
width: 100%;
height: 500px;
}
.company-item {
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.company-image-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.company-image {
object-fit: contain;
height : 100px;
}
.info-box {
border: 1px solid #3F415A;
border-radius: 10px;
height: max-content;
align-self : center;
justify-self : center;
padding : .2rem 0 ;
background-color: transparent;
}
.info-box-content {
display: flex;
flex-direction: column;
justify-content: space-around;
}
.info-row {
position : relative;
margin: 0rem 1rem;
display: flex;
gap : 1rem;
justify-content : space-between;
padding: 0rem .8rem;
direction: rtl;
&:has(.info-value.revenue) {border-bottom: 1px solid #3AEA83;}
&:has(.info-value.cost) {border-bottom: 1px solid #F76276;}
}
.info-label {
color: #FFFFFF;
font-size: 12px;
font-weight: 400;
text-align: right;
}
.info-value {
color: #34D399;
font-size: 14px;
font-weight: 600;
text-align: right;
margin-bottom : .5rem;
}
.info-value.revenue { color: #fff;}
.info-value.cost { color: #fff; }
.info-value.capacity { color: #fff; }
.info-unit {
position: absolute;
left: 12px;
bottom: 0;
color: #9CA3AF;
font-size: 8px;
font-weight: 400;
}
`}</style>
</div>
);
} }

View File

@ -60,7 +60,7 @@ export function DashboardCustomBarChart({
<span className="text-white font-bold text-base"> <span className="text-white font-bold text-base">
{formatNumber(item.value)} {formatNumber(item.value)}
</span> </span>
<span className="text-white font-persian font-medium text-sm w-max"> <span className="text-[#3F415A] font-persian font-medium text-sm w-max">
{item.label} {item.label}
</span> </span>
</div> </div>

View File

@ -126,19 +126,19 @@ 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); 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); 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); 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); 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); 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); 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;
console.log(costRed)
return { return {
category: rel, category: rel,
capacity: isFinite(capacityPct) ? capacityPct : 0, capacity: isFinite(capacityPct) ? capacityPct : 0,
@ -475,7 +475,7 @@ export function DashboardHome() {
<div className="flex items-center justify-center flex-col"> <div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="text-center"> <div className="text-center">
<p className="text-5xl font-bold text-green-400"> <p className="text-4xl font-bold text-green-400">
{formatNumber( {formatNumber(
dashboardData.topData dashboardData.topData
?.technology_innovation_based_revenue_growth || "0", ?.technology_innovation_based_revenue_growth || "0",
@ -487,7 +487,7 @@ export function DashboardHome() {
</div> </div>
<span className="text-6xl font-thin text-gray-600">/</span> <span className="text-6xl font-thin text-gray-600">/</span>
<div className="text-center"> <div className="text-center">
<p className="text-5xl font-bold text-green-400"> <p className="text-4xl font-bold text-green-400">
{formatNumber( {formatNumber(
Math.round( Math.round(
dashboardData.topData dashboardData.topData
@ -518,7 +518,7 @@ export function DashboardHome() {
<div className="flex items-center justify-center flex-col"> <div className="flex items-center justify-center flex-col">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="text-center"> <div className="text-center">
<p className="text-5xl font-bold text-green-400"> <p className="text-4xl font-bold text-green-400">
{formatNumber( {formatNumber(
Math.round( Math.round(
parseFloat( parseFloat(
@ -536,7 +536,7 @@ export function DashboardHome() {
</div> </div>
<span className="text-6xl font-thin text-gray-600">/</span> <span className="text-6xl font-thin text-gray-600">/</span>
<div className="text-center"> <div className="text-center">
<p className="text-5xl font-bold text-green-400"> <p className="text-4xl font-bold text-green-400">
{formatNumber( {formatNumber(
Math.round( Math.round(
dashboardData.topData dashboardData.topData
@ -642,7 +642,7 @@ export function DashboardHome() {
</ChartContainer> </ChartContainer>
<div className="font-bold font-persian text-center"> <div className="font-bold font-persian text-center">
<div className="flex flex-col justify-between items-center gap-2"> <div className="flex flex-col justify-between items-center gap-2">
<span className="flex font-bold items-center gap-1"> <span className="flex font-bold items-center gap-1 mr-auto">
<div className="font-light">مصوب :</div> <div className="font-light">مصوب :</div>
{formatNumber( {formatNumber(
Math.round( Math.round(
@ -655,7 +655,7 @@ export function DashboardHome() {
), ),
)} )}
</span> </span>
<span className="flex items-center gap-1 font-bold"> <span className="flex items-center gap-1 font-bold mr-auto">
<div className="font-light">جذب شده :</div> <div className="font-light">جذب شده :</div>
{formatNumber( {formatNumber(
Math.round( Math.round(
@ -700,7 +700,7 @@ export function DashboardHome() {
</TabsContent> </TabsContent>
<TabsContent value="canvas" className="w-ful h-full"> <TabsContent value="canvas" className="w-ful h-full">
<div className="p-4"> <div className="p-4 h-full">
<D3ImageInfo <D3ImageInfo
companies={ companies={
companyChartData.map((item) => { companyChartData.map((item) => {

View File

@ -11,6 +11,7 @@ import {
type ChartConfig, type ChartConfig,
ChartContainer, ChartContainer,
} from "~/components/ui/chart"; } from "~/components/ui/chart";
import { formatNumber } from "~/lib/utils";
export type CompanyChartDatum = { export type CompanyChartDatum = {
category: string; // related_company category: string; // related_company
@ -22,7 +23,7 @@ export type CompanyChartDatum = {
const chartConfig = { const chartConfig = {
capacity: { capacity: {
label: "افزایش ظرفیت", label: "افزایش ظرفیت",
color: "#60A5FA", // Blue-400 color: "#69C8EA",
}, },
revenue: { revenue: {
label: "افزایش درآمد", label: "افزایش درآمد",
@ -34,6 +35,7 @@ const chartConfig = {
}, },
} satisfies ChartConfig; } satisfies ChartConfig;
export function InteractiveBarChart({ export function InteractiveBarChart({
data, data,
}: { }: {
@ -47,7 +49,8 @@ export function InteractiveBarChart({
accessibilityLayer accessibilityLayer
data={data} data={data}
margin={{ left: 12, right: 12 }} margin={{ left: 12, right: 12 }}
barCategoryGap="42%" barGap={15}
barSize={8}
> >
<CartesianGrid vertical={false} stroke="#475569" /> <CartesianGrid vertical={false} stroke="#475569" />
<XAxis <XAxis
@ -64,14 +67,14 @@ export function InteractiveBarChart({
axisLine={false} axisLine={false}
tickMargin={8} tickMargin={8}
tick={{ fill: "#94a3b8", fontSize: 12 }} tick={{ fill: "#94a3b8", fontSize: 12 }}
tickFormatter={(value) => `${value}%`} tickFormatter={(value) => `${formatNumber(Math.round(value))}%`}
/> />
<Bar dataKey="capacity" fill={chartConfig.capacity.color} radius={[8, 8, 0, 0]}> <Bar dataKey="capacity" fill={chartConfig.capacity.color} radius={[8, 8, 0, 0]}>
<LabelList <LabelList
dataKey="capacity" dataKey="capacity"
position="top" position="top"
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }} style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
formatter={(v: number) => `${Math.round(v)}%`} formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
/> />
</Bar> </Bar>
<Bar dataKey="revenue" fill={chartConfig.revenue.color} radius={[8, 8, 0, 0]}> <Bar dataKey="revenue" fill={chartConfig.revenue.color} radius={[8, 8, 0, 0]}>
@ -79,7 +82,7 @@ export function InteractiveBarChart({
dataKey="revenue" dataKey="revenue"
position="top" position="top"
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }} style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
formatter={(v: number) => `${Math.round(v)}%`} formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
/> />
</Bar> </Bar>
<Bar dataKey="cost" fill={chartConfig.cost.color} radius={[8, 8, 0, 0]}> <Bar dataKey="cost" fill={chartConfig.cost.color} radius={[8, 8, 0, 0]}>
@ -87,7 +90,7 @@ export function InteractiveBarChart({
dataKey="cost" dataKey="cost"
position="top" position="top"
style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }} style={{ fill: "#ffffff", fontSize: "12px", fontWeight: "bold" }}
formatter={(v: number) => `${Math.round(v)}%`} formatter={(v: number) => `${formatNumber(Math.round(v))}%`}
/> />
</Bar> </Bar>
</BarChart> </BarChart>