fix: report bugs
This commit is contained in:
parent
1dd5ea70f4
commit
e36fbf9874
|
|
@ -1,9 +1,23 @@
|
|||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import moment from "moment-jalaali";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -12,39 +26,24 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "~/components/ui/table";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import {
|
||||
BarChart,
|
||||
Bar,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
|
||||
import apiService from "~/lib/api";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
LoaderCircle,
|
||||
TrendingUp,
|
||||
Key,
|
||||
Sparkle,
|
||||
Zap,
|
||||
Flame,
|
||||
Building2,
|
||||
PickaxeIcon,
|
||||
UsersIcon,
|
||||
UserIcon,
|
||||
RefreshCw,
|
||||
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Flame,
|
||||
Key,
|
||||
LoaderCircle,
|
||||
PickaxeIcon,
|
||||
RefreshCw,
|
||||
Sparkle,
|
||||
TrendingUp,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import apiService from "~/lib/api";
|
||||
import DashboardLayout from "../layout";
|
||||
|
||||
moment.loadPersian({ usePersianDigits: true });
|
||||
|
|
@ -100,7 +99,7 @@ interface InnovationStats {
|
|||
water_recovery_reduction_percent: number;
|
||||
average_project_score: number;
|
||||
count_innovation_green_projects: number;
|
||||
standard_regulations: string
|
||||
standard_regulations: string;
|
||||
}
|
||||
|
||||
interface Params {
|
||||
|
|
@ -174,7 +173,9 @@ export function GreenInnovationPage() {
|
|||
const [selectedProjects, setSelectedProjects] = useState<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
const [standartRegulation, setStandardRegulation] = useState<Array<string>>([])
|
||||
const [standartRegulation, setStandardRegulation] = useState<Array<string>>(
|
||||
[]
|
||||
);
|
||||
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
||||
const [selectedProjectDetails, setSelectedProjectDetails] =
|
||||
useState<GreenInnovationData | null>(null);
|
||||
|
|
@ -255,7 +256,6 @@ export function GreenInnovationPage() {
|
|||
};
|
||||
|
||||
const formatNumber = (value: string | number) => {
|
||||
if (!value) return "0";
|
||||
const numericValue = typeof value === "string" ? parseFloat(value) : value;
|
||||
if (isNaN(numericValue)) return "0";
|
||||
return new Intl.NumberFormat("fa-IR").format(numericValue);
|
||||
|
|
@ -269,7 +269,6 @@ export function GreenInnovationPage() {
|
|||
try {
|
||||
fetchingRef.current = true;
|
||||
if (reset) {
|
||||
|
||||
setCurrentPage(1);
|
||||
} else {
|
||||
setLoadingMore(true);
|
||||
|
|
@ -362,8 +361,11 @@ export function GreenInnovationPage() {
|
|||
useEffect(() => {
|
||||
fetchProjects(true);
|
||||
fetchTotalCount();
|
||||
}, [sortConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStats();
|
||||
}, [sortConfig, selectedProjects]);
|
||||
}, [selectedProjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPage > 1) {
|
||||
|
|
@ -398,7 +400,7 @@ export function GreenInnovationPage() {
|
|||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
}, [])
|
||||
}, []);
|
||||
const handleSort = (field: string) => {
|
||||
fetchingRef.current = false;
|
||||
setSortConfig((prev) => ({
|
||||
|
|
@ -457,12 +459,13 @@ export function GreenInnovationPage() {
|
|||
} catch {}
|
||||
}
|
||||
const parseNum = (v: unknown): any => {
|
||||
const convertNumber = typeof v === "number" ? Math.max(0, v) : 0;
|
||||
if (v == null) return 0;
|
||||
if (typeof v === "number") return v;
|
||||
if (typeof v === "number") return convertNumber;
|
||||
if (typeof v === "string") {
|
||||
const cleaned = v.replace(/,/g, "").trim();
|
||||
const n = parseFloat(cleaned);
|
||||
return isNaN(n) ? 0 : n;
|
||||
return isNaN(n) ? 0 : convertNumber;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
|
@ -503,9 +506,11 @@ export function GreenInnovationPage() {
|
|||
},
|
||||
avarage: stats.average_project_score,
|
||||
countInnovationGreenProjects: stats.count_innovation_green_projects,
|
||||
standardRegulation: stats.standard_regulations.replace('\r', '').split('\n')
|
||||
standardRegulation: stats.standard_regulations
|
||||
.replace("\r", "")
|
||||
.split("\n"),
|
||||
};
|
||||
setStandardRegulation(normalized.standardRegulation)
|
||||
setStandardRegulation(normalized.standardRegulation);
|
||||
setActualTotalCount(normalized.countInnovationGreenProjects);
|
||||
setTblAvarage(normalized.avarage);
|
||||
setPageData(normalized);
|
||||
|
|
@ -880,7 +885,8 @@ export function GreenInnovationPage() {
|
|||
position: "top",
|
||||
fill: "#fff",
|
||||
fontWeight: "bold",
|
||||
formatter: (value: any) => `${formatNumber(value)}%`,
|
||||
formatter: (value: any) =>
|
||||
`${formatNumber(value)}%`,
|
||||
}}
|
||||
/>
|
||||
</BarChart>
|
||||
|
|
@ -912,13 +918,19 @@ export function GreenInnovationPage() {
|
|||
<div className="flex flex-col gap-3 p-4 overflow-y-scroll h-[20rem]">
|
||||
{statsLoading
|
||||
? Array.from({ length: 10 }).map((_, index) => (
|
||||
<div key={`skeleton-${index}`} className="flex gap-2 items-center">
|
||||
<div
|
||||
key={`skeleton-${index}`}
|
||||
className="flex gap-2 items-center"
|
||||
>
|
||||
<span className="h-4 w-4 bg-gray-500/40 rounded-full animate-pulse"></span>
|
||||
<span className="h-3 w-32 bg-gray-500/40 rounded animate-pulse"></span>
|
||||
</div>
|
||||
))
|
||||
: standartRegulation.map((item, index) => (
|
||||
<div key={`${item}-${index}-1`} className="flex flex-row flex-1 gap-2">
|
||||
<div
|
||||
key={`${item}-${index}-1`}
|
||||
className="flex flex-row gap-2"
|
||||
>
|
||||
<LoaderCircle
|
||||
size={"20px"}
|
||||
height={"20px"}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import moment from "moment-jalaali";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -12,28 +18,29 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "~/components/ui/table";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
|
||||
import apiService from "~/lib/api";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
CodeXml,
|
||||
FilterIcon,
|
||||
Handshake,
|
||||
RefreshCw,
|
||||
SquareUser,
|
||||
User,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
FilterIcon,
|
||||
RefreshCw,
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
Handshake,
|
||||
CodeXml,
|
||||
SquareUser,
|
||||
Users,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
Customized,
|
||||
Line,
|
||||
LineChart,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
XAxis,
|
||||
} from "recharts";
|
||||
import apiService from "~/lib/api";
|
||||
import DashboardLayout from "../layout";
|
||||
import { LineChart, CartesianGrid, Legend, Line, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, Customized } from "recharts";
|
||||
|
||||
moment.loadPersian({ usePersianDigits: true });
|
||||
|
||||
|
|
@ -43,7 +50,7 @@ interface innovationBuiltInDate {
|
|||
done_date: string | null;
|
||||
observer: string;
|
||||
project_description: string;
|
||||
project_id: number | null;
|
||||
project_id: number | string;
|
||||
project_no: string;
|
||||
project_rating: string;
|
||||
project_status: string;
|
||||
|
|
@ -52,7 +59,7 @@ interface innovationBuiltInDate {
|
|||
}
|
||||
|
||||
interface DialogInfo {
|
||||
WorkflowID: number
|
||||
WorkflowID: number;
|
||||
collaboration_model: string;
|
||||
complexity_level: string;
|
||||
developer_team_role: string;
|
||||
|
|
@ -66,10 +73,9 @@ interface DialogInfo {
|
|||
role_company_staff: string | null;
|
||||
technology_maturity_level: string;
|
||||
title: string;
|
||||
technology_params?: Array<TechnologyParameter>
|
||||
technology_params?: Array<TechnologyParameter>;
|
||||
}
|
||||
|
||||
|
||||
interface SortConfig {
|
||||
field: string;
|
||||
direction: "asc" | "desc";
|
||||
|
|
@ -98,18 +104,17 @@ interface BottleNeckItem {
|
|||
value: string;
|
||||
description?: string;
|
||||
unit?: string;
|
||||
increasePercent: number
|
||||
increasePercent: number;
|
||||
};
|
||||
increaseIncome: {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
increasePercent: number
|
||||
increasePercent: number;
|
||||
unit?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
interface StatsCard {
|
||||
currencySaving: StateItem;
|
||||
investmentAmount: StateItem;
|
||||
|
|
@ -166,11 +171,10 @@ const columns = [
|
|||
];
|
||||
|
||||
const dialogChartData = [
|
||||
{ name: 'مرحه پیدایش', value: 10 },
|
||||
{ name: 'مرحله رشد', value: 14 },
|
||||
{ name: 'مرحله بلوغ', value: 25 },
|
||||
{ name:'مرحله افول', value: 15 },
|
||||
|
||||
{ name: "مرحه پیدایش", value: 10 },
|
||||
{ name: "مرحله رشد", value: 14 },
|
||||
{ name: "مرحله بلوغ", value: 25 },
|
||||
{ name: "مرحله افول", value: 15 },
|
||||
];
|
||||
|
||||
export function InnovationBuiltInsidePage() {
|
||||
|
|
@ -189,15 +193,13 @@ export function InnovationBuiltInsidePage() {
|
|||
direction: "asc",
|
||||
});
|
||||
const [tblAvarage, setTblAvarage] = useState<number>(0);
|
||||
const [selectedProjects, setSelectedProjects] = useState<Set<string>>(
|
||||
new Set()
|
||||
);
|
||||
const [selectedProjects, setSelectedProjects] = useState<
|
||||
Set<string | number>
|
||||
>(new Set());
|
||||
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
||||
const [selectedProjectDetails, setSelectedProjectDetails] =
|
||||
useState<DialogInfo>();
|
||||
|
||||
|
||||
|
||||
const [sustainabilityStats, setSustainabilityStats] = useState<StatsCard>({
|
||||
currencySaving: {
|
||||
id: "reduce-pollution",
|
||||
|
|
@ -228,35 +230,34 @@ export function InnovationBuiltInsidePage() {
|
|||
|
||||
const [bottleNeck, setBottleNeck] = useState<BottleNeckItem>({
|
||||
resolveBottleNeck: {
|
||||
label: 'تعدادگلوگاه رفع شده',
|
||||
value: '0',
|
||||
description: ''
|
||||
label: "تعدادگلوگاه رفع شده",
|
||||
value: "0",
|
||||
description: "",
|
||||
},
|
||||
increaseCapacity: {
|
||||
label: 'ظرفیت تولید اضافه شده',
|
||||
value: '0',
|
||||
description: 'درصد افزایش ظرفیت تولید',
|
||||
label: "ظرفیت تولید اضافه شده",
|
||||
value: "0",
|
||||
description: "درصد افزایش ظرفیت تولید",
|
||||
increasePercent: 0,
|
||||
unit: 'تن'
|
||||
unit: "تن",
|
||||
},
|
||||
increaseIncome: {
|
||||
label: 'میزان افزایش درآمد',
|
||||
value: '0',
|
||||
description: 'درصد افزایش درآمد',
|
||||
label: "میزان افزایش درآمد",
|
||||
value: "0",
|
||||
description: "درصد افزایش درآمد",
|
||||
increasePercent: 0,
|
||||
unit: "میلیون ریال"
|
||||
}
|
||||
})
|
||||
unit: "میلیون ریال",
|
||||
},
|
||||
});
|
||||
|
||||
const [showDialogItems, setShowDialogItems] = useState<boolean>(false);
|
||||
|
||||
const [showDialogItems, setShowDialogItems] = useState<boolean>(false)
|
||||
|
||||
const [countOfHighTech, setCountOfHighTech] = useState(0)
|
||||
const [countOfHighTech, setCountOfHighTech] = useState(0);
|
||||
|
||||
const observerRef = useRef<HTMLDivElement>(null);
|
||||
const fetchingRef = useRef(false);
|
||||
|
||||
const handleSelectProject = (projectNo: string) => {
|
||||
const handleSelectProject = (projectNo: string | number) => {
|
||||
const newSelected = new Set(selectedProjects);
|
||||
if (newSelected.has(projectNo)) {
|
||||
newSelected.delete(projectNo);
|
||||
|
|
@ -267,18 +268,16 @@ export function InnovationBuiltInsidePage() {
|
|||
};
|
||||
|
||||
const handleProjectDetails = async (project: DialogInfo) => {
|
||||
setShowDialogItems(true)
|
||||
setShowDialogItems(true);
|
||||
setDetailsDialogOpen(true);
|
||||
setSelectedProjectDetails(project);
|
||||
await fetchDialogTbl(project.WorkflowID)
|
||||
await fetchDialogTbl(project.WorkflowID);
|
||||
setTimeout(() => {
|
||||
setShowDialogItems(false)
|
||||
calculateProgressBar(+project.project_rating)
|
||||
setShowDialogItems(false);
|
||||
calculateProgressBar(+project.project_rating);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const formatNumber = (value: string | number) => {
|
||||
if (!value) return "0";
|
||||
const numericValue = typeof value === "string" ? parseFloat(value) : value;
|
||||
|
|
@ -315,7 +314,7 @@ export function InnovationBuiltInsidePage() {
|
|||
"role_company_staff",
|
||||
"number_employees_involved",
|
||||
"participants_full_name",
|
||||
"technology_maturity_level"
|
||||
"technology_maturity_level",
|
||||
],
|
||||
Sorts: [[sortConfig.field, sortConfig.direction]],
|
||||
Conditions: [["type_of_innovation", "=", "نوآوری ساخت داخل"]],
|
||||
|
|
@ -380,7 +379,6 @@ export function InnovationBuiltInsidePage() {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchDialogTbl = async (tblId: number) => {
|
||||
try {
|
||||
const response = await apiService.select({
|
||||
|
|
@ -388,7 +386,7 @@ export function InnovationBuiltInsidePage() {
|
|||
OutputFields: [
|
||||
"technology_parameter_title",
|
||||
"domestic_technology_parameter_value",
|
||||
"foreign_technology_parameter_value"
|
||||
"foreign_technology_parameter_value",
|
||||
],
|
||||
Conditions: [["project_id", "=", tblId]],
|
||||
});
|
||||
|
|
@ -398,7 +396,7 @@ export function InnovationBuiltInsidePage() {
|
|||
const parsedData = JSON.parse(dataString);
|
||||
setSelectedProjectDetails((prev: any) => ({
|
||||
...prev,
|
||||
technology_params: parsedData
|
||||
technology_params: parsedData,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -409,7 +407,9 @@ export function InnovationBuiltInsidePage() {
|
|||
};
|
||||
|
||||
const calculateProgressBar = (val: number) => {
|
||||
const pointer = document.getElementsByClassName('progressBarPointer')[0] as HTMLElement;
|
||||
const pointer = document.getElementsByClassName(
|
||||
"progressBarPointer"
|
||||
)[0] as HTMLElement;
|
||||
if (!pointer) return;
|
||||
|
||||
const leftValue = val !== 0 ? `calc(${val}% - 100px)` : `calc(0% - 40px)`;
|
||||
|
|
@ -430,7 +430,7 @@ export function InnovationBuiltInsidePage() {
|
|||
|
||||
useEffect(() => {
|
||||
fetchStats();
|
||||
}, [selectedProjects])
|
||||
}, [selectedProjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPage > 1) {
|
||||
|
|
@ -478,10 +478,9 @@ export function InnovationBuiltInsidePage() {
|
|||
setHasMore(true);
|
||||
};
|
||||
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
detailsDialogOpen
|
||||
detailsDialogOpen;
|
||||
setStatsLoading(true);
|
||||
const raw = await apiService.call<any>({
|
||||
innovation_construction_inside_function: {
|
||||
|
|
@ -515,7 +514,9 @@ export function InnovationBuiltInsidePage() {
|
|||
const normalized: any = {
|
||||
currencySaving: {
|
||||
value: formatNumber(parseNum(stats?.foreign_currency_saving)),
|
||||
percent: formatNumber(parseNum(stats?.foreign_currency_saving_percent)),
|
||||
percent: formatNumber(
|
||||
parseNum(stats?.foreign_currency_saving_percent)
|
||||
),
|
||||
},
|
||||
|
||||
investmentAmount: {
|
||||
|
|
@ -528,13 +529,21 @@ export function InnovationBuiltInsidePage() {
|
|||
},
|
||||
|
||||
income: {
|
||||
value: formatNumber(parseNum(stats.increased_income_after_innovation)),
|
||||
percent: formatNumber(parseNum(stats.increased_income_after_innovation_percent)),
|
||||
value: formatNumber(
|
||||
parseNum(stats.increased_income_after_innovation)
|
||||
),
|
||||
percent: formatNumber(
|
||||
parseNum(stats.increased_income_after_innovation_percent)
|
||||
),
|
||||
},
|
||||
|
||||
capacity: {
|
||||
value: formatNumber(parseNum(stats.increased_capacity_after_innovation)),
|
||||
percent: formatNumber(parseNum(stats.increased_capacity_after_innovation_percent)),
|
||||
value: formatNumber(
|
||||
parseNum(stats.increased_capacity_after_innovation)
|
||||
),
|
||||
percent: formatNumber(
|
||||
parseNum(stats.increased_capacity_after_innovation_percent)
|
||||
),
|
||||
},
|
||||
|
||||
resolveBottleNeck: {
|
||||
|
|
@ -542,7 +551,8 @@ export function InnovationBuiltInsidePage() {
|
|||
},
|
||||
countOfHighTech: formatNumber(stats.high_level_technology_count),
|
||||
avarage: stats.average_project_score,
|
||||
countInnovationGreenProjects: stats.count_innovation_construction_inside_projects,
|
||||
countInnovationGreenProjects:
|
||||
stats.count_innovation_construction_inside_projects,
|
||||
};
|
||||
setActualTotalCount(normalized.countInnovationGreenProjects);
|
||||
setTblAvarage(normalized.avarage);
|
||||
|
|
@ -559,7 +569,10 @@ export function InnovationBuiltInsidePage() {
|
|||
...prev,
|
||||
currencySaving: {
|
||||
...prev.currencySaving,
|
||||
total: { ...prev.currencySaving.total, value: normalized.currencySaving.value },
|
||||
total: {
|
||||
...prev.currencySaving.total,
|
||||
value: normalized.currencySaving.value,
|
||||
},
|
||||
percent: {
|
||||
...prev.currencySaving.percent,
|
||||
value: normalized.currencySaving.percent,
|
||||
|
|
@ -567,23 +580,29 @@ export function InnovationBuiltInsidePage() {
|
|||
},
|
||||
investmentAmount: {
|
||||
...prev.investmentAmount,
|
||||
total: { ...prev.investmentAmount.total, value: normalized.investmentAmount.value },
|
||||
percent: { ...prev.investmentAmount.percent, value: normalized.investmentAmount.percent },
|
||||
total: {
|
||||
...prev.investmentAmount.total,
|
||||
value: normalized.investmentAmount.value,
|
||||
},
|
||||
percent: {
|
||||
...prev.investmentAmount.percent,
|
||||
value: normalized.investmentAmount.percent,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
setBottleNeck(prev => ({
|
||||
setBottleNeck((prev) => ({
|
||||
...prev,
|
||||
|
||||
increaseIncome: {
|
||||
...prev.increaseIncome,
|
||||
value: normalized.income.value,
|
||||
increasePercent: normalized.income.percent
|
||||
increasePercent: normalized.income.percent,
|
||||
},
|
||||
increaseCapacity: {
|
||||
...prev.increaseCapacity,
|
||||
value: normalized.capacity.value,
|
||||
increasePercent: normalized.capacity.percent
|
||||
increasePercent: normalized.capacity.percent,
|
||||
},
|
||||
resolveBottleNeck: {
|
||||
...prev.resolveBottleNeck,
|
||||
|
|
@ -592,7 +611,7 @@ export function InnovationBuiltInsidePage() {
|
|||
// average: normalized.avarage,
|
||||
// countInnovationGreenProjects: normalized.countInnovationGreenProjects,
|
||||
}));
|
||||
setCountOfHighTech(normalized.countOfHighTech)
|
||||
setCountOfHighTech(normalized.countOfHighTech);
|
||||
};
|
||||
|
||||
const renderCellContent = (item: innovationBuiltInDate, column: any) => {
|
||||
|
|
@ -602,8 +621,8 @@ export function InnovationBuiltInsidePage() {
|
|||
case "select":
|
||||
return (
|
||||
<Checkbox
|
||||
checked={selectedProjects.has(item.project_id)}
|
||||
onCheckedChange={() => handleSelectProject(item.project_id)}
|
||||
checked={selectedProjects.has(item?.project_id!)}
|
||||
onCheckedChange={() => handleSelectProject(item?.project_id)}
|
||||
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600 cursor-pointer"
|
||||
/>
|
||||
);
|
||||
|
|
@ -688,7 +707,6 @@ export function InnovationBuiltInsidePage() {
|
|||
return el;
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<DashboardLayout title="نوآوری ساخت داخل">
|
||||
<div className="p-6 space-y-4 flex justify-between gap-8 sm:flex-col xl:flex-row">
|
||||
|
|
@ -760,9 +778,8 @@ export function InnovationBuiltInsidePage() {
|
|||
</Card>
|
||||
))}
|
||||
|
||||
|
||||
{
|
||||
statsLoading ? <div className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border border-gray-700/50 animate-pulse">
|
||||
{statsLoading ? (
|
||||
<div className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border border-gray-700/50 animate-pulse">
|
||||
<div className="p-0 h-full">
|
||||
<div className="flex flex-col justify-between gap-2 h-full">
|
||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 p-4 px-5">
|
||||
|
|
@ -788,7 +805,9 @@ export function InnovationBuiltInsidePage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> : <Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50">
|
||||
</div>
|
||||
) : (
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] rounded-lg backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="p-0 h-full">
|
||||
<div className="flex flex-col justify-between gap-2 h-full">
|
||||
<div className="flex justify-between items-center border-b-2 border-gray-500/20 p-4 px-5">
|
||||
|
|
@ -798,58 +817,57 @@ export function InnovationBuiltInsidePage() {
|
|||
<FilterIcon />
|
||||
</div>
|
||||
<div className="flex items-center justify-between p-6 px-14 flex-col gap-4">
|
||||
{
|
||||
Object.entries(bottleNeck).map(([key, value]) => {
|
||||
return <div key={`bottle-neck-${key}`} className="flex flex-row-reverse w-full justify-between">
|
||||
{Object.entries(bottleNeck).map(([key, value]) => {
|
||||
return (
|
||||
<div
|
||||
key={`bottle-neck-${key}`}
|
||||
className="flex flex-row-reverse w-full justify-between"
|
||||
>
|
||||
<div className="flex flex-col text-left text-gray-400">
|
||||
<span className="text-3xl font-bold text-emerald-500 mb-1 font-persian">
|
||||
{value.value}
|
||||
</span>
|
||||
{
|
||||
value.unit && <span className="text-sm font-persian">
|
||||
{value.unit && (
|
||||
<span className="text-sm font-persian">
|
||||
{value.unit}
|
||||
</span>
|
||||
}
|
||||
|
||||
)}
|
||||
</div>
|
||||
<div className="text-sm font-persian flex flex-col gap-1">
|
||||
<span className="text-lg">{value.label}</span>
|
||||
<div className="text-emerald-500 flex flex-row-reverse gap-1">
|
||||
<span>
|
||||
|
||||
{
|
||||
value.description && value.description
|
||||
}
|
||||
</span>
|
||||
<span>
|
||||
{value.increasePercent}
|
||||
{value.description && value.description}
|
||||
</span>
|
||||
<span>{value.increasePercent}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
}
|
||||
)}
|
||||
|
||||
{
|
||||
statsLoading ? <div className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] h-30 rounded-lg backdrop-blur-sm border border-gray-700/50 animate-pulse mt-4">
|
||||
{statsLoading ? (
|
||||
<div className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] h-30 rounded-lg backdrop-blur-sm border border-gray-700/50 animate-pulse mt-4">
|
||||
<div className="h-full flex flex-row justify-between p-6 items-center w-4/5 m-auto">
|
||||
<div className="h-6 w-32 bg-gray-600/40 rounded-md"></div>
|
||||
<div className="h-8 w-16 bg-gray-600/40 rounded-md"></div>
|
||||
</div>
|
||||
</div> : <Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] h-30 rounded-lg backdrop-blur-sm border-gray-700/50">
|
||||
</div>
|
||||
) : (
|
||||
<Card className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] h-30 rounded-lg backdrop-blur-sm border-gray-700/50">
|
||||
<CardContent className="h-full flex flex-row justify-between p-6 items-center w-4/5 m-auto">
|
||||
<span className="text-lg">تعداد فناوری سطح بالا</span>
|
||||
<span className="text-emerald-500 text-3xl font-bold font-persian">{countOfHighTech}</span>
|
||||
<span className="text-emerald-500 text-3xl font-bold font-persian">
|
||||
{countOfHighTech}
|
||||
</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
}
|
||||
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -960,13 +978,13 @@ export function InnovationBuiltInsidePage() {
|
|||
|
||||
<div className="p-2 px-4 bg-gray-700/50">
|
||||
<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-center items-center xl:w-full pr-6 sm:w-full">
|
||||
<div className="text-base text-gray-401 mb-1">
|
||||
کل پروژه ها :{formatNumber(actualTotalCount)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center flex-row gap-20 status justify-center xl:w-2/3 sm:w-full">
|
||||
<div className="flex items-center flex-row gap-5 status justify-start xl:w-full sm:w-full ">
|
||||
<div className="flex flex-row-reverse">
|
||||
<span className="block w-7 h-2.5 bg-violet-500 rounded-tl-xl rounded-bl-xl"></span>
|
||||
<span className="block w-7 h-2.5 bg-purple-500 "></span>
|
||||
|
|
@ -984,8 +1002,6 @@ export function InnovationBuiltInsidePage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
|
|
@ -999,32 +1015,30 @@ export function InnovationBuiltInsidePage() {
|
|||
</DialogHeader>
|
||||
<div className="flex justify-between text-right px-4">
|
||||
<div className="flex-[4] border-l-2 border-gray-600 pl-8">
|
||||
|
||||
{
|
||||
showDialogItems ? <div className="animate-pulse flex flex-col gap-2">
|
||||
{showDialogItems ? (
|
||||
<div className="animate-pulse flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<div className="h-4 w-full bg-gray-600 rounded"></div>
|
||||
<div className="h-4 w-5/6 bg-gray-600 rounded"></div>
|
||||
<div className="h-4 w-11/12 bg-gray-600 rounded"></div>
|
||||
</div>
|
||||
</div> : <div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<h2 className="font-bold">{selectedProjectDetails?.title}</h2>
|
||||
<p className="text-gray-300 font-persian mt-2 text-justify">
|
||||
{selectedProjectDetails?.project_description || "-"}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
<h2 className="font-bold my-6">دانش فنی محصول جدید</h2>
|
||||
{
|
||||
showDialogItems ? <div className="newProductTechKnowledge flex flex-col gap-4 h-max w-full animate-pulse">
|
||||
{showDialogItems ? (
|
||||
<div className="newProductTechKnowledge flex flex-col gap-4 h-max w-full animate-pulse">
|
||||
<div className="w-max relative">
|
||||
<div className="range flex flex-row-reverse rounded-xl overflow-hidden justify-center">
|
||||
<div className="bg-gray-500 w-28 h-10 p-4 text-center flex items-center justify-center">
|
||||
</div>
|
||||
<div className="bg-gray-400 w-28 h-10 p-4 text-center flex items-center justify-center">
|
||||
</div>
|
||||
<div className="bg-gray-300 w-28 h-10 p-4 text-center flex items-center justify-center">
|
||||
</div>
|
||||
<div className="bg-gray-500 w-28 h-10 p-4 text-center flex items-center justify-center"></div>
|
||||
<div className="bg-gray-400 w-28 h-10 p-4 text-center flex items-center justify-center"></div>
|
||||
<div className="bg-gray-300 w-28 h-10 p-4 text-center flex items-center justify-center"></div>
|
||||
</div>
|
||||
|
||||
<div className="progressBarPointer absolute z-[200] top-0.5 left-0.5">
|
||||
|
|
@ -1036,7 +1050,9 @@ export function InnovationBuiltInsidePage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> : <div className="newProductTechKnowledge flex flex-col gap-4 h-max w-full ">
|
||||
</div>
|
||||
) : (
|
||||
<div className="newProductTechKnowledge flex flex-col gap-4 h-max w-full ">
|
||||
<div className="w-max relative transition-all duration-300">
|
||||
<div className="range flex flex-row-reverse rounded-xl overflow-hidden justify-center">
|
||||
<div className="bg-emerald-700 w-28 h-10 p-4 text-center text-sm flex items-center justify-center">
|
||||
|
|
@ -1049,20 +1065,23 @@ export function InnovationBuiltInsidePage() {
|
|||
<span className="text-gray-700">سطح بالا</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='progressBarPointer absolute z-[200] top-0.5 transition-all duration-300'>
|
||||
<div className="progressBarPointer absolute z-[200] top-0.5 transition-all duration-300">
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
<span className="block w-0.5 h-14 bg-white"></span>
|
||||
<span className="text-white border border-white p-1 px-2 text-xs rounded-lg"> سطح تکنولوژی</span>
|
||||
<span className="text-white border border-white p-1 px-2 text-xs rounded-lg">
|
||||
{" "}
|
||||
سطح تکنولوژی
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-5 mt-20">
|
||||
<h2 className="font-bold">مشارکت در پروژه</h2>
|
||||
{
|
||||
showDialogItems ? <div className="space-y-3">
|
||||
{showDialogItems ? (
|
||||
<div className="space-y-3">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<div key={i} className="flex flex-row gap-2 items-center">
|
||||
<div className="h-5 w-5 bg-gray-500 rounded-full"></div>
|
||||
|
|
@ -1072,34 +1091,45 @@ export function InnovationBuiltInsidePage() {
|
|||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div> : <div className="flex flex-col gap-5">
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="content flex flex-col gap-3 w-5/6">
|
||||
<div className="flex flex-row gap-2 w-full">
|
||||
<Handshake className="text-emerald-500 w-5" />
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<span>مدل همکاری:</span>
|
||||
<span>{selectedProjectDetails?.collaboration_model}</span>
|
||||
<span>
|
||||
{selectedProjectDetails?.collaboration_model}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 w-full">
|
||||
<CodeXml className="text-emerald-500 w-5" />
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<span>نقش تیم توسعه دهنده:</span>
|
||||
<span>{selectedProjectDetails?.developer_team_role}</span>
|
||||
<span>
|
||||
{selectedProjectDetails?.developer_team_role}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 w-full">
|
||||
<SquareUser className="text-emerald-500 w-5" />
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<span>نقش کارکنان شرکت: </span>
|
||||
<span>{selectedProjectDetails?.role_company_staff ?? '-'}</span>
|
||||
<span>
|
||||
{selectedProjectDetails?.role_company_staff ?? "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 w-full">
|
||||
<Users className="text-emerald-500 w-5" />
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<span>تعداد کارکنان درگیر: </span>
|
||||
<span className="persian-font">{selectedProjectDetails?.number_employees_involved ?? 0}</span>
|
||||
<span className="persian-font">
|
||||
{selectedProjectDetails?.number_employees_involved ??
|
||||
0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1108,59 +1138,76 @@ export function InnovationBuiltInsidePage() {
|
|||
<User className="text-emerald-500 w-5" />
|
||||
<div className="flex flex-col justify-between w-full gap-1">
|
||||
<span>لیست کارکنان : </span>
|
||||
<span className="pr-1">{selectedProjectDetails?.participants_full_name}</span>
|
||||
<span className="pr-1">
|
||||
{selectedProjectDetails?.participants_full_name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Project Details */}
|
||||
<div className="px-4 pl-0 flex flex-col gap-4 w-1/2">
|
||||
{
|
||||
showDialogItems ? <div className="flex flex-col gap-2 animate-pulse">
|
||||
{showDialogItems ? (
|
||||
<div className="flex flex-col gap-2 animate-pulse">
|
||||
{[...Array(5)].map((_, rowIndex) => (
|
||||
<div key={`skeleton-${rowIndex}`}>
|
||||
<div className="h-6 bg-gray-700 rounded"></div>
|
||||
</div>
|
||||
))}
|
||||
</div> : <div className="tbl rounded-xl overflow-hidden">
|
||||
</div>
|
||||
) : (
|
||||
<div className="tbl rounded-xl overflow-hidden">
|
||||
<div className="grid grid-cols-3 bg-[#3F415A] text-white">
|
||||
<span className="text-md text-center p-3">شاخص مقایسه با نمونه خارجی</span>
|
||||
<span className="text-md text-center p-3">
|
||||
شاخص مقایسه با نمونه خارجی
|
||||
</span>
|
||||
<span className="text-md text-center p-3">نمونه داخلی</span>
|
||||
<span className="text-md text-center p-3">نمونه خارجی</span>
|
||||
</div>
|
||||
<div className="flex flex-col divide-y divide-gray-600 overflow-auto max-h-[6rem]">
|
||||
{
|
||||
selectedProjectDetails?.technology_params?.map((el, index) => {
|
||||
return <div className="grid grid-cols-3 py-3" key={`technology-${index}-${el.WorkflowID}`}>
|
||||
<span className="text-center">{el.technology_parameter_title}</span>
|
||||
{selectedProjectDetails?.technology_params?.map(
|
||||
(el, index) => {
|
||||
return (
|
||||
<div
|
||||
className="grid grid-cols-3 py-3"
|
||||
key={`technology-${index}-${el.WorkflowID}`}
|
||||
>
|
||||
<span className="text-center">
|
||||
{el.technology_parameter_title}
|
||||
</span>
|
||||
<div className="flex flex-row items-center gap-1 justify-center">
|
||||
<span>{formatNumber(el.domestic_technology_parameter_value)}</span>
|
||||
<span className="text-sm text-gray-400">میلیون لیتر</span>
|
||||
<span>
|
||||
{formatNumber(
|
||||
el.domestic_technology_parameter_value
|
||||
)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-400">
|
||||
میلیون لیتر
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-1 justify-center">
|
||||
<span>{formatNumber(el.foreign_technology_parameter_value)}</span>
|
||||
<span className="text-sm text-gray-400">میلیون لیتر</span>
|
||||
<span>
|
||||
{formatNumber(
|
||||
el.foreign_technology_parameter_value
|
||||
)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-400">
|
||||
میلیون لیتر
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
})
|
||||
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div style={{ width: '100%', height: 400 }}>
|
||||
{
|
||||
showDialogItems ? <div className="relative h-96 w-full bg-gray-700 rounded-lg overflow-hidden animate-pulse">
|
||||
)}
|
||||
<div style={{ width: "100%", height: 400 }}>
|
||||
{showDialogItems ? (
|
||||
<div className="relative h-96 w-full bg-gray-700 rounded-lg overflow-hidden animate-pulse">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
|
|
@ -1177,14 +1224,16 @@ export function InnovationBuiltInsidePage() {
|
|||
key={i}
|
||||
className="absolute bg-gray-500 rounded-full"
|
||||
style={{
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
width: "10px",
|
||||
height: "10px",
|
||||
left: `${10 + i * 18}%`,
|
||||
top: `${30 + (i % 2) * 20}%`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div> : <ResponsiveContainer width="100%" height={420}>
|
||||
</div>
|
||||
) : (
|
||||
<ResponsiveContainer width="100%" height={420}>
|
||||
<LineChart
|
||||
data={dialogChartData}
|
||||
margin={{ top: 20, right: 20, left: 20, bottom: 80 }}
|
||||
|
|
@ -1220,19 +1269,27 @@ export function InnovationBuiltInsidePage() {
|
|||
const xAxis: any = xAxes[0];
|
||||
const ticks = xAxis?.ticks || [];
|
||||
|
||||
const value = selectedProjectDetails?.technology_maturity_level;
|
||||
const value =
|
||||
selectedProjectDetails?.technology_maturity_level;
|
||||
|
||||
const xFromScale = typeof xAxis?.scale === "function" ? xAxis.scale(value) : undefined;
|
||||
const xFromScale =
|
||||
typeof xAxis?.scale === "function"
|
||||
? xAxis.scale(value)
|
||||
: undefined;
|
||||
|
||||
const tick = ticks.find(
|
||||
(t: any) =>
|
||||
t &&
|
||||
(t.value === value || (t.payload && t.payload.value === value))
|
||||
(t.value === value ||
|
||||
(t.payload && t.payload.value === value))
|
||||
);
|
||||
|
||||
const axisOffsetX = xAxis?.x ?? 0;
|
||||
|
||||
const x = (xFromScale ?? tick?.coordinate ?? width / 2) + axisOffsetX - 15;
|
||||
const x =
|
||||
(xFromScale ?? tick?.coordinate ?? width / 2) +
|
||||
axisOffsetX -
|
||||
15;
|
||||
|
||||
const rectWidth = 140;
|
||||
const rectHeight = 28;
|
||||
|
|
@ -1267,10 +1324,9 @@ export function InnovationBuiltInsidePage() {
|
|||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,30 @@
|
|||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { DashboardLayout } from "../layout";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Building2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
CirclePause,
|
||||
DollarSign,
|
||||
Funnel,
|
||||
PickaxeIcon,
|
||||
RefreshCw,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import moment from "moment-jalaali";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card, CardContent } from "~/components/ui/card";
|
||||
import { Checkbox } from "~/components/ui/checkbox";
|
||||
import { CustomBarChart } from "~/components/ui/custom-bar-chart";
|
||||
import moment from "moment-jalaali";
|
||||
import type { BarChartData } from "~/components/ui/custom-bar-chart";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -15,29 +33,14 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "~/components/ui/table";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import {
|
||||
ChevronUp,
|
||||
ChevronDown,
|
||||
RefreshCw,
|
||||
ExternalLink,
|
||||
Building2,
|
||||
PickaxeIcon,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
} from "lucide-react";
|
||||
import apiService from "~/lib/api";
|
||||
import toast from "react-hot-toast";
|
||||
import { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react";
|
||||
import { formatNumber } from "~/lib/utils";
|
||||
import { DashboardLayout } from "../layout";
|
||||
|
||||
moment.loadPersian({ usePersianDigits: true });
|
||||
interface ProcessInnovationData {
|
||||
project_no: string;
|
||||
project_id: string;
|
||||
title: string;
|
||||
project_status: string;
|
||||
project_rating: string;
|
||||
|
|
@ -48,18 +51,31 @@ interface ProcessInnovationData {
|
|||
observer: string;
|
||||
}
|
||||
|
||||
interface ProjectStats {
|
||||
average_project_score: string;
|
||||
count_innovation_process_projects: number;
|
||||
count_throat_removal: number;
|
||||
percent_reducing_breakdowns: string;
|
||||
percent_reduction_value_currency: string;
|
||||
percent_sum_stopping_production: string;
|
||||
percent_throat_removal: string;
|
||||
sum_reducing_breakdowns: number;
|
||||
sum_reduction_value_currency: number;
|
||||
sum_stopping_production: number;
|
||||
}
|
||||
|
||||
interface SortConfig {
|
||||
field: string;
|
||||
direction: "asc" | "desc";
|
||||
}
|
||||
|
||||
interface StatsCard {
|
||||
id: string;
|
||||
title: string;
|
||||
value: string;
|
||||
description: string;
|
||||
icon: React.ReactNode;
|
||||
color: string;
|
||||
enum projectStatus {
|
||||
propozal = "پروپوزال",
|
||||
contract = "پیشنویس قرارداد",
|
||||
inprogress = "در حال انجام",
|
||||
stop = "متوقف شده",
|
||||
mafasa = "مرحله مفاصا",
|
||||
finish = "پایان یافته",
|
||||
}
|
||||
|
||||
interface InnovationStats {
|
||||
|
|
@ -69,10 +85,10 @@ interface InnovationStats {
|
|||
bottleneckRemovalCount: number; // تعداد رفع گلوگاه
|
||||
currencyReductionSum: number; // مجموع کاهش ارز بری (میلیون ریال)
|
||||
frequentFailuresReductionSum: number; // مجموع کاهش خرابی های پرتکرار
|
||||
percentProductionStops: number; // درصد مقایسهای جلوگیری از توقفات تولید
|
||||
percentBottleneckRemoval: number; // درصد مقایسهای رفع گلوگاه
|
||||
percentCurrencyReduction: number; // درصد مقایسهای کاهش ارز بری
|
||||
percentFailuresReduction: number; // درصد مقایسهای کاهش خرابیهای پرتکرار
|
||||
percentProductionStops: number | string; // درصد مقایسهای جلوگیری از توقفات تولید
|
||||
percentBottleneckRemoval: number | string; // درصد مقایسهای رفع گلوگاه
|
||||
percentCurrencyReduction: number | string; // درصد مقایسهای کاهش ارز بری
|
||||
percentFailuresReduction: number | string; // درصد مقایسهای کاهش خرابیهای پرتکرار
|
||||
}
|
||||
|
||||
const columns = [
|
||||
|
|
@ -126,6 +142,50 @@ export function ProcessInnovationPage() {
|
|||
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
||||
const [selectedProjectDetails, setSelectedProjectDetails] =
|
||||
useState<ProcessInnovationData | null>(null);
|
||||
|
||||
const [stateCard, setStateCard] = useState({
|
||||
productionstopsprevention: {
|
||||
id: "productionstopsprevention",
|
||||
title: "جلوگیری از توقفات تولید",
|
||||
value: formatNumber(
|
||||
stats.productionStopsPreventionSum.toFixed?.(1) ??
|
||||
stats.productionStopsPreventionSum
|
||||
),
|
||||
description: "تن افزایش یافته",
|
||||
icon: <CirclePause />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
bottleneckremoval: {
|
||||
id: "bottleneckremoval",
|
||||
title: "رفع گلوگاه",
|
||||
value: formatNumber(stats.bottleneckRemovalCount),
|
||||
description: "تعداد رفع گلوگاه",
|
||||
icon: <Funnel />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
currencyreduction: {
|
||||
id: "currencyreduction",
|
||||
title: "کاهش ارز بری",
|
||||
value: formatNumber(
|
||||
stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum
|
||||
),
|
||||
description: "دلار کاهش یافته",
|
||||
icon: <DollarSign />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
frequentfailuresreduction: {
|
||||
id: "frequentfailuresreduction",
|
||||
title: "کاهش خرابی های پرتکرار",
|
||||
value: formatNumber(
|
||||
stats.frequentFailuresReductionSum.toFixed?.(1) ??
|
||||
stats.frequentFailuresReductionSum
|
||||
),
|
||||
description: "مجموع درصد کاهش خرابی",
|
||||
icon: <Wrench />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
});
|
||||
|
||||
const observerRef = useRef<HTMLDivElement>(null);
|
||||
const fetchingRef = useRef(false);
|
||||
|
||||
|
|
@ -153,58 +213,7 @@ export function ProcessInnovationPage() {
|
|||
setDetailsDialogOpen(true);
|
||||
};
|
||||
|
||||
const formatNumber = (value: string | number) => {
|
||||
if (!value) return "0";
|
||||
const numericValue = typeof value === "string" ? parseFloat(value) : value;
|
||||
if (isNaN(numericValue)) return "0";
|
||||
return new Intl.NumberFormat("fa-IR").format(numericValue);
|
||||
};
|
||||
|
||||
// Stats cards data - computed from projects data
|
||||
const statsCards: StatsCard[] = [
|
||||
{
|
||||
id: "production-stops-prevention",
|
||||
title: "جلوگیری از توقفات تولید",
|
||||
value: formatNumber(
|
||||
stats.productionStopsPreventionSum.toFixed?.(1) ??
|
||||
stats.productionStopsPreventionSum
|
||||
),
|
||||
description: "تن افزایش یافته",
|
||||
icon: <CirclePause />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
{
|
||||
id: "bottleneck-removal",
|
||||
title: "رفع گلوگاه",
|
||||
value: formatNumber(stats.bottleneckRemovalCount),
|
||||
description: "تعداد رفع گلوگاه",
|
||||
icon: <Funnel />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
|
||||
{
|
||||
id: "currency-reduction",
|
||||
title: "کاهش ارز بری",
|
||||
value: formatNumber(
|
||||
stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum
|
||||
),
|
||||
description: "دلار کاهش یافته",
|
||||
icon: <DollarSign />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
{
|
||||
id: "frequent-failures-reduction",
|
||||
title: "کاهش خرابی های پرتکرار",
|
||||
value: formatNumber(
|
||||
stats.frequentFailuresReductionSum.toFixed?.(1) ??
|
||||
stats.frequentFailuresReductionSum
|
||||
),
|
||||
description: "مجموع درصد کاهش خرابی",
|
||||
icon: <Wrench />,
|
||||
color: "text-emerald-400",
|
||||
},
|
||||
];
|
||||
|
||||
const fetchProjects = async (reset = false) => {
|
||||
if (fetchingRef.current) {
|
||||
return;
|
||||
|
|
@ -226,6 +235,7 @@ export function ProcessInnovationPage() {
|
|||
ProcessName: "project",
|
||||
OutputFields: [
|
||||
"project_no",
|
||||
"project_id",
|
||||
"title",
|
||||
"project_status",
|
||||
"project_rating",
|
||||
|
|
@ -313,9 +323,12 @@ export function ProcessInnovationPage() {
|
|||
useEffect(() => {
|
||||
fetchProjects(true);
|
||||
fetchTotalCount();
|
||||
fetchStats();
|
||||
}, [sortConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchStats();
|
||||
}, [selectedProjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPage > 1) {
|
||||
fetchProjects(false);
|
||||
|
|
@ -393,7 +406,12 @@ export function ProcessInnovationPage() {
|
|||
try {
|
||||
setStatsLoading(true);
|
||||
const raw = await apiService.call<any>({
|
||||
innovation_process_function: {},
|
||||
innovation_process_function: {
|
||||
project_ids:
|
||||
selectedProjects.size > 0
|
||||
? Array.from(selectedProjects).join(" , ")
|
||||
: "",
|
||||
},
|
||||
});
|
||||
|
||||
let payload: any = raw?.data;
|
||||
|
|
@ -403,40 +421,53 @@ export function ProcessInnovationPage() {
|
|||
} catch {}
|
||||
}
|
||||
|
||||
const parseNum = (v: unknown): number => {
|
||||
const parseNum = (v: unknown): any => {
|
||||
const convertNumber = typeof v === "number" ? Math.max(0, v) : 0;
|
||||
if (v == null) return 0;
|
||||
if (typeof v === "number") return v;
|
||||
if (typeof v === "number") return convertNumber;
|
||||
if (typeof v === "string") {
|
||||
const cleaned = v.replace(/,/g, "").trim();
|
||||
const n = parseFloat(cleaned);
|
||||
return isNaN(n) ? 0 : n;
|
||||
return isNaN(n) ? 0 : convertNumber;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const data: Array<ProjectStats> = JSON.parse(
|
||||
payload?.innovation_process_function
|
||||
);
|
||||
const stats = data[0];
|
||||
const normalized: InnovationStats = {
|
||||
totalProjects: parseNum(payload?.count_innovation_process_projects),
|
||||
averageScore: parseNum(payload?.average_project_score),
|
||||
productionStopsPreventionSum: parseNum(
|
||||
payload?.sum_stopping_production
|
||||
),
|
||||
bottleneckRemovalCount: parseNum(payload?.count_throat_removal),
|
||||
currencyReductionSum: parseNum(payload?.sum_reduction_value_currency),
|
||||
frequentFailuresReductionSum: parseNum(
|
||||
payload?.sum_reducing_breakdowns
|
||||
),
|
||||
percentProductionStops: parseNum(
|
||||
payload?.percent_sum_stopping_production
|
||||
),
|
||||
percentBottleneckRemoval: parseNum(payload?.percent_throat_removal),
|
||||
percentCurrencyReduction: parseNum(
|
||||
payload?.percent_reduction_value_currency
|
||||
),
|
||||
percentFailuresReduction: parseNum(
|
||||
payload?.percent_reducing_breakdowns
|
||||
),
|
||||
totalProjects: parseNum(stats?.count_innovation_process_projects),
|
||||
averageScore: parseNum(stats?.average_project_score),
|
||||
productionStopsPreventionSum: parseNum(stats?.sum_stopping_production),
|
||||
bottleneckRemovalCount: parseNum(stats?.count_throat_removal),
|
||||
currencyReductionSum: parseNum(stats?.sum_reduction_value_currency),
|
||||
frequentFailuresReductionSum: parseNum(stats?.sum_reducing_breakdowns),
|
||||
percentProductionStops: stats?.percent_sum_stopping_production,
|
||||
percentBottleneckRemoval: stats?.percent_throat_removal,
|
||||
percentCurrencyReduction: stats?.percent_reduction_value_currency,
|
||||
percentFailuresReduction: stats?.percent_reducing_breakdowns,
|
||||
};
|
||||
|
||||
setStateCard((prev) => ({
|
||||
...prev,
|
||||
bottleneckremoval: {
|
||||
...prev.bottleneckremoval,
|
||||
value: formatNumber(normalized.bottleneckRemovalCount),
|
||||
},
|
||||
productionstopsprevention: {
|
||||
...prev.productionstopsprevention,
|
||||
value: formatNumber(normalized.productionStopsPreventionSum),
|
||||
},
|
||||
frequentfailuresreduction: {
|
||||
...prev.frequentfailuresreduction,
|
||||
value: formatNumber(normalized.frequentFailuresReductionSum),
|
||||
},
|
||||
currencyreduction: {
|
||||
...prev.currencyreduction,
|
||||
value: formatNumber(normalized.currencyReductionSum),
|
||||
},
|
||||
}));
|
||||
setStats(normalized);
|
||||
} catch (error) {
|
||||
console.error("Error fetching stats:", error);
|
||||
|
|
@ -445,16 +476,6 @@ export function ProcessInnovationPage() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchingRef.current = false;
|
||||
setCurrentPage(1);
|
||||
setProjects([]);
|
||||
setHasMore(true);
|
||||
fetchProjects(true);
|
||||
fetchTotalCount();
|
||||
fetchStats();
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: string | number) => {
|
||||
if (!amount) return "0 ریال";
|
||||
const numericAmount =
|
||||
|
|
@ -465,34 +486,28 @@ export function ProcessInnovationPage() {
|
|||
return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال";
|
||||
};
|
||||
|
||||
const formatPercentage = (value: string | number) => {
|
||||
if (!value) return "0%";
|
||||
const numericValue = typeof value === "string" ? parseFloat(value) : value;
|
||||
if (isNaN(numericValue)) return "0%";
|
||||
return `${numericValue.toFixed(1)}%`;
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case "فعال":
|
||||
return "#3AEA83";
|
||||
case "متوقف":
|
||||
return "#F76276";
|
||||
case "تکمیل شده":
|
||||
return "#32CD32";
|
||||
default:
|
||||
return "#6B7280";
|
||||
const statusColor = (status: projectStatus): any => {
|
||||
let el = null;
|
||||
switch (status) {
|
||||
case projectStatus.contract:
|
||||
el = "teal";
|
||||
break;
|
||||
case projectStatus.finish:
|
||||
el = "info";
|
||||
break;
|
||||
case projectStatus.stop:
|
||||
el = "warning";
|
||||
break;
|
||||
case projectStatus.inprogress:
|
||||
el = "teal";
|
||||
break;
|
||||
case projectStatus.mafasa:
|
||||
el = "destructive";
|
||||
break;
|
||||
case projectStatus.propozal:
|
||||
el = "info";
|
||||
}
|
||||
};
|
||||
|
||||
const getRatingColor = (rating: string) => {
|
||||
const ratingNum = parseFloat(rating);
|
||||
if (isNaN(ratingNum)) return "#6B7280";
|
||||
|
||||
if (ratingNum >= 8) return "#3AEA83";
|
||||
if (ratingNum >= 6) return "#69C8EA";
|
||||
if (ratingNum >= 4) return "#FFD700";
|
||||
return "#F76276";
|
||||
return el;
|
||||
};
|
||||
|
||||
const renderCellContent = (item: ProcessInnovationData, column: any) => {
|
||||
|
|
@ -502,8 +517,8 @@ export function ProcessInnovationPage() {
|
|||
case "select":
|
||||
return (
|
||||
<Checkbox
|
||||
checked={selectedProjects.has(item.project_no)}
|
||||
onCheckedChange={() => handleSelectProject(item.project_no)}
|
||||
checked={selectedProjects.has(item.project_id)}
|
||||
onCheckedChange={() => handleSelectProject(item.project_id)}
|
||||
className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600"
|
||||
/>
|
||||
);
|
||||
|
|
@ -534,15 +549,16 @@ export function ProcessInnovationPage() {
|
|||
return <span className="font-medium text-white">{String(value)}</span>;
|
||||
case "project_status":
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="font-medium border-2"
|
||||
variant={statusColor(value)}
|
||||
className="font-medium border-2 p-0 block w-2 h-2 rounded-full"
|
||||
style={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
></Badge>
|
||||
{String(value)}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
case "project_rating":
|
||||
return (
|
||||
|
|
@ -603,7 +619,7 @@ export function ProcessInnovationPage() {
|
|||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
: statsCards.map((card) => (
|
||||
: Object.entries(stateCard).map(([key, card]) => (
|
||||
<Card
|
||||
key={card.id}
|
||||
className="bg-[linear-gradient(to_bottom_left,#464861,50%,#111628)] backdrop-blur-sm border-gray-700/50"
|
||||
|
|
@ -792,61 +808,32 @@ export function ProcessInnovationPage() {
|
|||
</div>
|
||||
</CardContent>
|
||||
|
||||
{/* Selection Summary */}
|
||||
{/* {selectedProjects.size > 0 && (
|
||||
<div className="px-4 py-3 bg-emerald-500/10 border-t border-emerald-500/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-2 h-2 bg-emerald-500 rounded-full"></div>
|
||||
<span className="text-emerald-400 font-medium font-persian">
|
||||
{selectedProjects.size} پروژه انتخاب شده
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSelectedProjects(new Set())}
|
||||
className="border-emerald-500/30 text-emerald-400 hover:bg-emerald-500/20 hover:text-emerald-300"
|
||||
>
|
||||
لغو انتخاب
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
{/* Footer */}
|
||||
|
||||
<div className="p-2 px-4 bg-gray-700/50">
|
||||
<div className="grid grid-cols-6 gap-4 text-sm text-gray-300 font-persian">
|
||||
<div className="text-center gap-2 items-center flex">
|
||||
<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-base text-gray-401 mb-1">
|
||||
{" "}
|
||||
کل پروژه ها :{" "}
|
||||
{formatNumber(stats.totalProjects || actualTotalCount)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Project number column - empty */}
|
||||
<div></div>
|
||||
{/* Title column - empty */}
|
||||
<div></div>
|
||||
{/* Project status column - empty */}
|
||||
<div></div>
|
||||
{/* Project rating column - show average */}
|
||||
<div className="flex justify-center items-center gap-2">
|
||||
<div className="text-base text-gray-400 mb-1">
|
||||
{" "}
|
||||
میانگین امتیاز :
|
||||
</div>
|
||||
<div className="font-bold">
|
||||
{formatNumber(
|
||||
((stats.averageScore ?? 0) as number).toFixed?.(1) ??
|
||||
stats.averageScore ??
|
||||
0
|
||||
)}
|
||||
کل پروژه ها :{formatNumber(actualTotalCount)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Details column - show total count */}
|
||||
<div className="flex items-center flex-row gap-20 status justify-center xl:w-2/3 sm:w-full">
|
||||
<div className="flex flex-row-reverse">
|
||||
<span className="block w-7 h-2.5 bg-violet-500 rounded-tl-xl rounded-bl-xl"></span>
|
||||
<span className="block w-7 h-2.5 bg-purple-500 "></span>
|
||||
<span className="block w-7 h-2.5 bg-cyan-300 "></span>
|
||||
<span className="block w-7 h-2.5 bg-pink-400 rounded-tr-xl rounded-br-xl"></span>
|
||||
</div>
|
||||
<div className="flex justify-center items-center gap-2">
|
||||
<div className="text-base text-gray-400 mb-1">میانگین :</div>
|
||||
<div className="font-bold">
|
||||
{formatNumber(
|
||||
((stats.averageScore ?? 0) as number).toFixed?.(1) ?? 0
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import * as React from "react";
|
||||
import { formatNumber } from "~/lib/utils";
|
||||
|
||||
export interface BarChartData {
|
||||
|
|
@ -80,7 +79,7 @@ export function CustomBarChart({
|
|||
{data.map((item, index) => {
|
||||
const percentage =
|
||||
globalMaxValue > 0 ? (item.value / globalMaxValue) * 100 : 0;
|
||||
const displayValue = item.value.toFixed(1);
|
||||
const displayValue: any = item.value;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-center gap-3">
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export function cn(...inputs: ClassValue[]) {
|
|||
}
|
||||
|
||||
export const formatNumber = (value: string | number) => {
|
||||
if (!value) return "0";
|
||||
// if (!value) return "0";
|
||||
const numericValue = typeof value === "string" ? parseFloat(value) : value;
|
||||
if (isNaN(numericValue)) return "0";
|
||||
return new Intl.NumberFormat("fa-IR").format(numericValue);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user