diff --git a/app/components/dashboard/dashboard-home.tsx b/app/components/dashboard/dashboard-home.tsx index 94c6a26..a53b119 100644 --- a/app/components/dashboard/dashboard-home.tsx +++ b/app/components/dashboard/dashboard-home.tsx @@ -44,9 +44,15 @@ export function DashboardHome() { const [date, setDate] = useStoredDate(); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { + const handler = (date: CalendarDate) => { if (date) setDate(date); - }); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => { diff --git a/app/components/dashboard/header.tsx b/app/components/dashboard/header.tsx index d654fc9..d32a18c 100644 --- a/app/components/dashboard/header.tsx +++ b/app/components/dashboard/header.tsx @@ -1,18 +1,23 @@ +import { saveAs } from "file-saver"; import jalaali from "jalaali-js"; import { Calendar, ChevronLeft, + FileChartColumnIncreasing, Menu, PanelLeft, Server, User, } from "lucide-react"; import React, { useEffect, useRef, useState } from "react"; +import { useLocation } from "react-router"; +import XLSX from "xlsx-js-style"; import { Button } from "~/components/ui/button"; import { Calendar as CustomCalendar } from "~/components/ui/Calendar"; import { useAuth } from "~/contexts/auth-context"; import apiService from "~/lib/api"; import { cn, EventBus, handleDataValue } from "~/lib/utils"; + interface HeaderProps { onToggleSidebar?: () => void; className?: string; @@ -66,6 +71,115 @@ const monthList: Array = [ }, ]; +const columns: Array = [ + { key: "title", label: "عنوان پروژه", sortable: true, width: "300px" }, + { + key: "importance_project", + label: "میزان اهمیت", + sortable: true, + width: "160px", + }, + { + key: "strategic_theme", + label: "مضمون راهبردی", + sortable: true, + width: "200px", + }, + { + key: "value_technology_and_innovation", + label: "ارزش فناوری و نوآوری", + sortable: true, + width: "220px", + }, + { + key: "type_of_innovation", + label: "انواع نوآوری", + sortable: true, + width: "160px", + }, + { + key: "innovation", + label: "میزان نوآوری", + sortable: true, + width: "140px", + }, + { + key: "person_executing", + label: "مسئول اجرا", + sortable: true, + width: "180px", + }, + { + key: "excellent_observer", + label: "ناطر عالی", + sortable: true, + width: "180px", + }, + { key: "observer", label: "ناظر پروژه", sortable: true, width: "180px" }, + { key: "moderator", label: "مجری", sortable: true, width: "180px" }, + { + key: "executive_phase", + label: "فاز اجرایی", + sortable: true, + width: "160px", + }, + { + key: "start_date", + label: "تاریخ شروع", + sortable: true, + width: "120px", + }, + { + key: "remaining_time", + label: "زمان باقی مانده", + sortable: true, + width: "140px", + computed: true, + }, + { + key: "end_date", + label: "تاریخ پایان (برنامه‌ریزی)", + sortable: true, + width: "160px", + }, + { + key: "renewed_duration", + label: "مدت زمان تمدید", + sortable: true, + width: "140px", + }, + { + key: "done_date", + label: "تاریخ پایان (واقعی)", + sortable: true, + width: "160px", + }, + { + key: "deviation_from_program", + label: "متوسط انحراف برنامه‌ای", + sortable: true, + width: "160px", + }, + { + key: "approved_budget", + label: "بودجه مصوب", + sortable: true, + width: "150px", + }, + { + key: "budget_spent", + label: "بودجه صرف شده", + sortable: true, + width: "150px", + }, + { + key: "cost_deviation", + label: "متوسط انحراف هزینه‌ای", + sortable: true, + width: "160px", + }, +]; + export function Header({ onToggleSidebar, className, @@ -79,6 +193,9 @@ export function Header({ const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false); const [isNotificationOpen, setIsNotificationOpen] = useState(false); const [openCalendar, setOpenCalendar] = useState(false); + const [excelLoading, setExcelLoading] = useState(false); + const location = useLocation(); + const projectManagerRoute = "/dashboard/project-management"; const [currentYear, setCurrentYear] = useState({ since: jy, until: jy, @@ -209,6 +326,66 @@ export function Header({ }; }, []); + const exportToExcel = async () => { + let arr = []; + const data: any = await fetchExcelData(); + for (let i = 0; i < data.length; i++) { + let obj: Record = {}; + const project = data[i]; + + Object.entries(project).forEach(([pKey, pValue]: [any, any]) => { + Object.values(columns).forEach((col) => { + if (pKey === col?.key) { + ``; + obj[col?.label] = handleDataValue( + pValue?.includes(",") ? pValue.replaceAll(",", "") : pValue + ); + } + }); + }); + + arr.push(obj); + } + + const worksheet = XLSX.utils.json_to_sheet(arr); + + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, "People"); + + const excelBuffer = XLSX.write(workbook, { + bookType: "xlsx", + type: "array", + }); + + const blob = new Blob([excelBuffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + saveAs(blob, "reports.xls"); + }; + + const fetchExcelData = async () => { + setExcelLoading(true); + const fetchableColumns = columns.filter((c) => !c.computed); + const outputFields = fetchableColumns.map((c) => c.apiField ?? c.key); + + const response = await apiService.select({ + ProcessName: "project", + OutputFields: outputFields, + Conditions: [ + ["start_date", ">=", selectedDate?.start || null, "and"], + ["start_date", "<=", selectedDate?.end || null], + ], + }); + const parsedData = JSON.parse(response.data); + setExcelLoading(false); + return parsedData; + }; + + const handleDownloadFile = () => { + if (excelLoading) return null; + else exportToExcel(); + }; + return (
+ {location.pathname === projectManagerRoute ? ( +
+ + + دانلود فایل اکسل + +
+ ) : ( + "" + )} + {user?.id === 2041 && (
+ {/* Profile Dropdown */} {isProfileMenuOpen && (
diff --git a/app/components/dashboard/project-management/digital-innovation-page.tsx b/app/components/dashboard/project-management/digital-innovation-page.tsx index 2ba5dc0..3f894e2 100644 --- a/app/components/dashboard/project-management/digital-innovation-page.tsx +++ b/app/components/dashboard/project-management/digital-innovation-page.tsx @@ -359,19 +359,6 @@ export function DigitalInnovationPage() { } }, [hasMore, loading, loadingMore]); - // useEffect(() => { - // const storedDate = localStorage.getItem("dateSelected"); - - // if (storedDate) { - // setDate(JSON.parse(storedDate)); - // } else { - // setDate({ - // start: `${jy}/01/01`, - // end: `${jy}/12/30`, - // }); - // } - // }, []); - useEffect(() => { if (date?.start && date?.end) { fetchTable(true); @@ -381,11 +368,15 @@ export function DigitalInnovationPage() { }, [sortConfig, date]); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => { diff --git a/app/components/dashboard/project-management/green-innovation-page.tsx b/app/components/dashboard/project-management/green-innovation-page.tsx index feaa986..2216fe7 100644 --- a/app/components/dashboard/project-management/green-innovation-page.tsx +++ b/app/components/dashboard/project-management/green-innovation-page.tsx @@ -361,11 +361,15 @@ export function GreenInnovationPage() { }; useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); const loadMore = useCallback(() => { diff --git a/app/components/dashboard/project-management/innovation-built-inside-page.tsx b/app/components/dashboard/project-management/innovation-built-inside-page.tsx index 691eb12..4cb5eef 100644 --- a/app/components/dashboard/project-management/innovation-built-inside-page.tsx +++ b/app/components/dashboard/project-management/innovation-built-inside-page.tsx @@ -426,11 +426,15 @@ export function InnovationBuiltInsidePage() { }, [hasMore, loading]); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => { diff --git a/app/components/dashboard/project-management/mange-ideas-tech-page.tsx b/app/components/dashboard/project-management/mange-ideas-tech-page.tsx index 63bd863..a0aca7e 100644 --- a/app/components/dashboard/project-management/mange-ideas-tech-page.tsx +++ b/app/components/dashboard/project-management/mange-ideas-tech-page.tsx @@ -405,11 +405,15 @@ export function ManageIdeasTechPage() { }, [hasMore, loading, loadingMore]); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => { diff --git a/app/components/dashboard/project-management/process-innovation-page.tsx b/app/components/dashboard/project-management/process-innovation-page.tsx index 24d4a7d..3232833 100644 --- a/app/components/dashboard/project-management/process-innovation-page.tsx +++ b/app/components/dashboard/project-management/process-innovation-page.tsx @@ -340,11 +340,15 @@ export function ProcessInnovationPage() { }, [hasMore, loading]); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => { diff --git a/app/components/dashboard/project-management/project-management-page.tsx b/app/components/dashboard/project-management/project-management-page.tsx index 9101d13..07474d4 100644 --- a/app/components/dashboard/project-management/project-management-page.tsx +++ b/app/components/dashboard/project-management/project-management-page.tsx @@ -1,15 +1,9 @@ import { saveAs } from "file-saver"; -import { - ChevronDown, - ChevronUp, - FileChartColumnIncreasing, - RefreshCw, -} from "lucide-react"; +import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import toast from "react-hot-toast"; import XLSX from "xlsx-js-style"; import { Badge } from "~/components/ui/badge"; -import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { Table, @@ -289,11 +283,15 @@ export function ProjectManagementPage() { }; useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); const loadMore = useCallback(() => { if (hasMore && !loading && !loadingMore && !fetchingRef.current) { @@ -801,11 +799,13 @@ export function ProjectManagementPage() { // const totalPages = Math.ceil(totalCount / pageSize); - const exportToExcel = () => { + const exportToExcel = async () => { let arr = []; - for (let i = 0; i < projects.length; i++) { + const data = await fetchExcelData(); + debugger; + for (let i = 0; i < data.length; i++) { let obj: Record = {}; - const project = projects[i]; + const project = data[i]; Object.entries(project).forEach(([pKey, pValue]) => { Object.values(columns).forEach((col) => { @@ -840,10 +840,31 @@ export function ProjectManagementPage() { saveAs(blob, "people.xls"); }; + const fetchExcelData = async () => { + const fetchableColumns = columns.filter((c) => !c.computed); + const outputFields = fetchableColumns.map((c) => c.apiField ?? c.key); + const sortCol = columns.find((c) => c.key === sortConfig.field); + const sortField = sortCol?.computed + ? undefined + : (sortCol?.apiField ?? sortCol?.key); + + const response = await apiService.select({ + ProcessName: "project", + OutputFields: outputFields, + Sorts: sortField ? [[sortField, sortConfig.direction]] : [], + Conditions: [ + ["start_date", ">=", date?.start || null, "and"], + ["start_date", "<=", date?.end || null], + ], + }); + const parsedData = JSON.parse(response.data); + return parsedData; + }; + return (
-
+ {/*
-
+
*/} {/* Data Table */} diff --git a/app/components/dashboard/strategic-alignment-popup.tsx b/app/components/dashboard/strategic-alignment-popup.tsx index ff16e65..aa4ce93 100644 --- a/app/components/dashboard/strategic-alignment-popup.tsx +++ b/app/components/dashboard/strategic-alignment-popup.tsx @@ -136,11 +136,15 @@ export function StrategicAlignmentPopup({ }, [open]); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); const fetchData = async () => { diff --git a/app/components/ecosystem/info-panel.tsx b/app/components/ecosystem/info-panel.tsx index 4627082..a403f19 100644 --- a/app/components/ecosystem/info-panel.tsx +++ b/app/components/ecosystem/info-panel.tsx @@ -68,11 +68,15 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) { const [date, setDate] = useStoredDate(); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => { diff --git a/app/components/ecosystem/network-graph.tsx b/app/components/ecosystem/network-graph.tsx index e0b10eb..8d446a9 100644 --- a/app/components/ecosystem/network-graph.tsx +++ b/app/components/ecosystem/network-graph.tsx @@ -78,11 +78,15 @@ export function NetworkGraph({ const [date, setDate] = useStoredDate(); useEffect(() => { - EventBus.on("dateSelected", (date: CalendarDate) => { - if (date) { - setDate(date); - } - }); + const handler = (date: CalendarDate) => { + if (date) setDate(date); + }; + + EventBus.on("dateSelected", handler); + + return () => { + EventBus.off("dateSelected", handler); + }; }, []); useEffect(() => {