From ba7a2499f1d1bc8cd5593ff691f702998692d409 Mon Sep 17 00:00:00 2001 From: Saeed Abadiyan Date: Tue, 19 Aug 2025 06:22:03 +0330 Subject: [PATCH] add popup for innvation ,also fix the zoom initial in graph popup --- .../process-innovation-page.tsx | 685 ++++++++++-------- app/components/ecosystem/network-graph.tsx | 24 +- package.json | 2 - pnpm-lock.yaml | 34 - 4 files changed, 382 insertions(+), 363 deletions(-) diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx index 7016940..0190f3b 100644 --- a/app/components/dashboard/project-management/process-innovation-page.tsx +++ b/app/components/dashboard/project-management/process-innovation-page.tsx @@ -5,6 +5,7 @@ import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; 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 { Table, @@ -25,11 +26,17 @@ import { 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 { Funnel, Wrench, CirclePause, DollarSign } from "lucide-react"; +import ProjectDetail from "../projects/project-detail"; +moment.loadPersian({ usePersianDigits: true }); interface ProcessInnovationData { project_no: string; title: string; @@ -39,6 +46,7 @@ interface ProcessInnovationData { throat_removal: string; amount_currency_reduction: string; Reduce_rate_failure: string; + observer: string; } interface SortConfig { @@ -72,9 +80,19 @@ const columns = [ { key: "select", label: "", sortable: false, width: "50px" }, { key: "project_no", label: "شماره پروژه", sortable: true, width: "140px" }, { key: "title", label: "عنوان پروژه", sortable: true, width: "400px" }, - { key: "project_status", label: "وضعیت پروژه", sortable: true, width: "140px" }, - { key: "project_rating", label: "امتیاز پروژه", sortable: true, width: "140px" }, - { key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" } + { + key: "project_status", + label: "وضعیت پروژه", + sortable: true, + width: "140px", + }, + { + key: "project_rating", + label: "امتیاز پروژه", + sortable: true, + width: "140px", + }, + { key: "details", label: "جزئیات پروژه", sortable: false, width: "140px" }, ]; export function ProcessInnovationPage() { @@ -103,9 +121,12 @@ export function ProcessInnovationPage() { field: "start_date", direction: "asc", }); - const [selectedProjects, setSelectedProjects] = useState>(new Set()); + const [selectedProjects, setSelectedProjects] = useState>( + new Set(), + ); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [selectedProjectDetails, setSelectedProjectDetails] = useState(null); + const [selectedProjectDetails, setSelectedProjectDetails] = + useState(null); const observerRef = useRef(null); const fetchingRef = useRef(false); @@ -114,7 +135,7 @@ export function ProcessInnovationPage() { if (selectedProjects.size === projects.length) { setSelectedProjects(new Set()); } else { - setSelectedProjects(new Set(projects.map(p => p.project_no))); + setSelectedProjects(new Set(projects.map((p) => p.project_no))); } }; @@ -129,6 +150,7 @@ export function ProcessInnovationPage() { }; const handleProjectDetails = (project: ProcessInnovationData) => { + console.log(project); setSelectedProjectDetails(project); setDetailsDialogOpen(true); }; @@ -145,9 +167,12 @@ export function ProcessInnovationPage() { { id: "production-stops-prevention", title: "جلوگیری از توقفات تولید", - value: formatNumber(stats.productionStopsPreventionSum.toFixed?.(1) ?? stats.productionStopsPreventionSum), + value: formatNumber( + stats.productionStopsPreventionSum.toFixed?.(1) ?? + stats.productionStopsPreventionSum, + ), description: "ظرفیت افزایش یافته", - icon: , + icon: , color: "text-emerald-400", }, { @@ -156,25 +181,30 @@ export function ProcessInnovationPage() { value: formatNumber(stats.bottleneckRemovalCount), description: "تعداد رفع گلوگاه", icon: , - color: "text-emerald-400" + color: "text-emerald-400", }, - + { id: "currency-reduction", title: "کاهش ارز بری", - value: formatNumber(stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum), + value: formatNumber( + stats.currencyReductionSum.toFixed?.(0) ?? stats.currencyReductionSum, + ), description: "دلار کاهش یافته", - icon: , + icon: , color: "text-emerald-400", }, { id: "frequent-failures-reduction", title: "کاهش خرابیهای پرتکرار", - value: formatNumber(stats.frequentFailuresReductionSum.toFixed?.(1) ?? stats.frequentFailuresReductionSum), + value: formatNumber( + stats.frequentFailuresReductionSum.toFixed?.(1) ?? + stats.frequentFailuresReductionSum, + ), description: "مجموع درصد کاهش خرابی", - icon: , + icon: , color: "text-emerald-400", - } + }, ]; const fetchProjects = async (reset = false) => { @@ -184,7 +214,7 @@ export function ProcessInnovationPage() { try { fetchingRef.current = true; - + if (reset) { setLoading(true); setCurrentPage(1); @@ -201,16 +231,22 @@ export function ProcessInnovationPage() { "title", "project_status", "project_rating", - "reduce_prevention_production_stops", "throat_removal", + "reduce_prevention_production_stops", "amount_currency_reduction", "Reduce_rate_failure", + "project_description", + "start_date", + "done_date", + "approved_budget", + "observer", ], - Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, - Sorts: [[sortConfig.field, sortConfig.direction]], + Sorts: [["start_date", "asc"]], Conditions: [["type_of_innovation", "=", "نوآوری در فرآیند"]], + Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, }); + console.log(JSON.parse(response.data)); if (response.state === 0) { const dataString = response.data; if (dataString && typeof dataString === "string") { @@ -289,26 +325,26 @@ export function ProcessInnovationPage() { }, [currentPage]); useEffect(() => { - const scrollContainer = document.querySelector('.overflow-auto'); - + const scrollContainer = document.querySelector(".overflow-auto"); + const handleScroll = () => { if (!scrollContainer || !hasMore || loadingMore) return; - + const { scrollTop, scrollHeight, clientHeight } = scrollContainer; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; - + if (scrollPercentage >= 0.9) { loadMore(); } }; if (scrollContainer) { - scrollContainer.addEventListener('scroll', handleScroll); + scrollContainer.addEventListener("scroll", handleScroll); } return () => { if (scrollContainer) { - scrollContainer.removeEventListener('scroll', handleScroll); + scrollContainer.removeEventListener("scroll", handleScroll); } }; }, [loadMore, hasMore, loadingMore]); @@ -359,13 +395,14 @@ export function ProcessInnovationPage() { try { setStatsLoading(true); const raw = await apiService.callInnovationProcess({ - innovation_process_function: { - }, + innovation_process_function: {}, }); let payload: any = raw?.data; if (typeof payload === "string") { - try { payload = JSON.parse(payload); } catch {} + try { + payload = JSON.parse(payload); + } catch {} } const parseNum = (v: unknown): number => { @@ -382,14 +419,24 @@ export function ProcessInnovationPage() { const normalized: InnovationStats = { totalProjects: parseNum(payload?.count_innovation_process_projects), averageScore: parseNum(payload?.average_project_score), - productionStopsPreventionSum: parseNum(payload?.sum_stopping_production), + 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), + 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), + percentCurrencyReduction: parseNum( + payload?.percent_reduction_value_currency, + ), + percentFailuresReduction: parseNum( + payload?.percent_reducing_breakdowns, + ), }; setStats(normalized); @@ -443,7 +490,7 @@ export function ProcessInnovationPage() { 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"; @@ -481,10 +528,7 @@ export function ProcessInnovationPage() { ); case "project_no": return ( - + {String(value)} ); @@ -496,7 +540,7 @@ export function ProcessInnovationPage() { variant="outline" className="font-medium border-2" style={{ - border:"none", + border: "none", }} > {String(value)} @@ -504,10 +548,7 @@ export function ProcessInnovationPage() { ); case "project_rating": return ( - + {formatNumber(String(value))} ); @@ -529,187 +570,215 @@ export function ProcessInnovationPage() {
{/* Stats Cards */}
-
- {/* Stats Grid */} -
- {loading || statsLoading ? ( - // Loading skeleton for stats cards - matching new design - Array.from({ length: 4 }).map((_, index) => ( - - -
-
-
-
-
+
+ {/* Stats Grid */} +
+ {loading || statsLoading + ? // Loading skeleton for stats cards - matching new design + Array.from({ length: 4 }).map((_, index) => ( + + +
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- - - )) - ) : ( - statsCards.map((card) => ( - - -
-
-

- {card.title} -

-
- {card.icon} + + + )) + : statsCards.map((card) => ( + + +
+
+

+ {card.title} +

+
+ {card.icon} +
+
+
+

+ {card.value} +

+

+ {card.description} +

+
-
-
-

- {card.value} -

-

- {card.description} -

- -
-
- - - )) - )} + + + ))} +
-
- {/* Process Impacts Chart */} - - - - - -
+ {/* Process Impacts Chart */} + + + + + +
{/* Data Table */}
- - - - {columns.map((column) => ( - - {column.key === "select" ? ( -
- 0} - onCheckedChange={handleSelectAll} - className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600" +
+ + + {columns.map((column) => ( + + {column.key === "select" ? ( +
+ 0 + } + onCheckedChange={handleSelectAll} + className="data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600" + /> +
+ ) : column.sortable ? ( + + ) : ( + column.label + )} +
+ ))} +
+
+ + {loading ? ( + // Skeleton loading rows (compact) + Array.from({ length: 10 }).map((_, index) => ( + + {columns.map((column) => ( + +
+
+
- ) : column.sortable ? ( - - ) : ( - column.label - )} - - ))} - - - - {loading ? ( - // Skeleton loading rows (compact) - Array.from({ length: 10 }).map((_, index) => ( - - {columns.map((column) => ( - -
-
-
-
- - ))} - - )) - ) : projects.length === 0 ? ( - - - - هیچ پروژه‌ای یافت نشد - - + + ))} - ) : ( - projects.map((project, index) => ( - - {columns.map((column) => ( - - {renderCellContent(project, column)} - - ))} - - )) - )} - -
+ )) + ) : projects.length === 0 ? ( + + + + هیچ پروژه‌ای یافت نشد + + + + ) : ( + projects.map((project, index) => ( + + {columns.map((column) => ( + + {renderCellContent(project, column)} + + ))} + + )) + )} + +
{/* Infinite scroll trigger */} @@ -718,8 +787,7 @@ export function ProcessInnovationPage() {
- - +
)} @@ -752,8 +820,12 @@ export function ProcessInnovationPage() { {/* Footer */}
-
-
کل پروژه ها : {formatNumber(stats.totalProjects || actualTotalCount)}
+
+
+ {" "} + کل پروژه ها :{" "} + {formatNumber(stats.totalProjects || actualTotalCount)} +
{/* Project number column - empty */}
@@ -763,128 +835,105 @@ export function ProcessInnovationPage() {
{/* Project rating column - show average */}
-
میانگین امتیاز :‌
+
+ {" "} + میانگین امتیاز :‌ +
- {formatNumber(((stats.averageScore ?? 0) as number).toFixed?.(1) ?? (stats.averageScore ?? 0))} + {formatNumber( + ((stats.averageScore ?? 0) as number).toFixed?.(1) ?? + stats.averageScore ?? + 0, + )}
- + {/* Details column - show total count */} -
- -
{/* Project Details Dialog */} - + - - جزئیات پروژه + + شرح پروژه - - {selectedProjectDetails && ( -
- {/* Project Header */} -
-

- {selectedProjectDetails.title} -

-
- - {selectedProjectDetails.project_no} - - - {selectedProjectDetails.project_status} - -
+
+ {/* Project Description */} +
+

{selectedProjectDetails?.title}

+

+ {selectedProjectDetails?.project_description || "-"} +

+
+ + {/* Project Details */} +
+
جزئیات پروژه
+ +
+

+ + زمان شروع: +

+ + {selectedProjectDetails?.start_date + ? moment( + selectedProjectDetails?.start_date, + "YYYY-MM-DD", + ).format("YYYY/MM/DD") + : "-"} +
- {/* Project Metrics */} -
-
-

- امتیاز پروژه -

- - {formatNumber(selectedProjectDetails.project_rating)} - -
- -
-

- کاهش توقفات تولید -

- - {formatNumber(selectedProjectDetails.reduce_prevention_production_stops)} - -
- -
-

- رفع گلوگاه تولید -

- - {formatNumber(selectedProjectDetails.throat_removal)} - -
- -
-

- کاهش ارز بری -

- - {formatCurrency(selectedProjectDetails.amount_currency_reduction)} - -
- -
-

- کاهش خرابی پر تکرار -

- - {formatNumber(selectedProjectDetails.Reduce_rate_failure)} - -
+
+

+ + زمان پایان: +

+ + {selectedProjectDetails?.done_date + ? moment( + selectedProjectDetails?.done_date, + "YYYY-MM-DD", + ).format("YYYY/MM/DD") + : "-"} +
- {/* Action Buttons */} -
- +
+

+ + هزینه برآورد شده: +

+ + {formatNumber( + Number( + selectedProjectDetails?.approved_budget.replaceAll( + ",", + "", + ), + ), + ) || "-"} + +
+
+

+ + نفر مرتبط: +

+ + {selectedProjectDetails?.observer || "-"} +
- )} +
); -} \ No newline at end of file +} diff --git a/app/components/ecosystem/network-graph.tsx b/app/components/ecosystem/network-graph.tsx index 709012a..41fc9e3 100644 --- a/app/components/ecosystem/network-graph.tsx +++ b/app/components/ecosystem/network-graph.tsx @@ -199,7 +199,7 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { // Create zoom behavior const zoom = d3 .zoom() - .scaleExtent([1, 2.5]) // Limit zoom out to 1x, zoom in to 2.5x + .scaleExtent([0.8, 2.5]) // Limit zoom out to 1x, zoom in to 2.5x .on("zoom", (event) => { container.attr("transform", event.transform); }); @@ -240,6 +240,18 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { d3.forceCollide().radius((d) => (d.isCenter ? 40 : 30)), ); + const initialScale = 0.85; + const initialTranslate = [ + width / 2 - (width / 2) * initialScale, + height / 2 - (height / 2) * initialScale, + ]; + svg.call( + zoom.transform, + d3.zoomIdentity + .translate(initialTranslate[0], initialTranslate[1]) + .scale(initialScale), + ); + // Fix center node position const centerNode = nodes.find((n) => n.isCenter); if (centerNode) { @@ -424,19 +436,13 @@ export function NetworkGraph({ onNodeClick }: NetworkGraphProps) { // Filter out image fields and find description const filteredFields = fieldValues.filter( (field: any) => - ![ - "image", - "img", - "des", - "dec", - "description", - "collaboration", - ].includes(field.F.toLowerCase()), + !["image", "img", "full_name"].includes(field.F.toLowerCase()), ); const descriptionField = fieldValues.find( (field: any) => field.F.toLowerCase().includes("description") || + field.F.toLowerCase().includes("collaboration") || field.F.toLowerCase().includes("about"), ); diff --git a/package.json b/package.json index 33415d2..4356f86 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "@radix-ui/react-slot": "^1.0.2", "@react-router/node": "^7.7.0", "@react-router/serve": "^7.7.1", - "@sigma/node-image": "^3.0.0", "@types/d3": "^7.4.3", "chart.js": "^4.5.0", "class-variance-authority": "^0.7.1", @@ -32,7 +31,6 @@ "react-hot-toast": "^2.5.2", "react-router": "^7.7.0", "recharts": "^3.1.2", - "sigma": "^3.0.2", "tailwind-merge": "^3.3.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c72c9e..8dbe9a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,6 @@ importers: '@react-router/serve': specifier: ^7.7.1 version: 7.8.0(react-router@7.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(typescript@5.8.3) - '@sigma/node-image': - specifier: ^3.0.0 - version: 3.0.0(sigma@3.0.2(graphology-types@0.24.8)) '@types/d3': specifier: ^7.4.3 version: 7.4.3 @@ -77,9 +74,6 @@ importers: recharts: specifier: ^3.1.2 version: 3.1.2(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react-is@19.1.1)(react@19.1.0)(redux@5.0.1) - sigma: - specifier: ^3.0.2 - version: 3.0.2(graphology-types@0.24.8) tailwind-merge: specifier: ^3.3.1 version: 3.3.1 @@ -974,11 +968,6 @@ packages: cpu: [x64] os: [win32] - '@sigma/node-image@3.0.0': - resolution: {integrity: sha512-i4WLNPugDY4jgQEZtNSiSVj4HHXOraciXLtlgdygeUxMVEhH8PJ/+Q1vQ9f/SlKFnZQ+7vH3HnsSDW6FD9aP+g==} - peerDependencies: - sigma: '>=3.0.0-beta.10' - '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -1659,11 +1648,6 @@ packages: graphology-types@0.24.8: resolution: {integrity: sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==} - graphology-utils@2.5.2: - resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==} - peerDependencies: - graphology-types: '>=0.23.0' - graphology@0.26.0: resolution: {integrity: sha512-8SSImzgUUYC89Z042s+0r/vMibY7GX/Emz4LDO5e7jYXhuoWfHISPFJYjpRLUSJGq6UQ6xlenvX1p/hJdfXuXg==} peerDependencies: @@ -2198,9 +2182,6 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - sigma@3.0.2: - resolution: {integrity: sha512-/BUbeOwPGruiBOm0YQQ6ZMcLIZ6tf/W+Jcm7dxZyAX0tK3WP9/sq7/NAWBxPIxVahdGjCJoGwej0Gdrv0DxlQQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3282,10 +3263,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.45.1': optional: true - '@sigma/node-image@3.0.0(sigma@3.0.2(graphology-types@0.24.8))': - dependencies: - sigma: 3.0.2(graphology-types@0.24.8) - '@standard-schema/spec@1.0.0': {} '@standard-schema/utils@0.3.0': {} @@ -4009,10 +3986,6 @@ snapshots: graphology-types@0.24.8: {} - graphology-utils@2.5.2(graphology-types@0.24.8): - dependencies: - graphology-types: 0.24.8 - graphology@0.26.0(graphology-types@0.24.8): dependencies: events: 3.3.0 @@ -4503,13 +4476,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - sigma@3.0.2(graphology-types@0.24.8): - dependencies: - events: 3.3.0 - graphology-utils: 2.5.2(graphology-types@0.24.8) - transitivePeerDependencies: - - graphology-types - signal-exit@4.1.0: {} source-map-js@1.2.1: {}