fix: change download excel

This commit is contained in:
MehrdadAdabi 2025-10-25 19:03:19 +03:30
parent 344d2a36f4
commit 5ba67aa240
11 changed files with 311 additions and 73 deletions

View File

@ -44,9 +44,15 @@ export function DashboardHome() {
const [date, setDate] = useStoredDate(); const [date, setDate] = useStoredDate();
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) setDate(date); if (date) setDate(date);
}); };
EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -1,18 +1,23 @@
import { saveAs } from "file-saver";
import jalaali from "jalaali-js"; import jalaali from "jalaali-js";
import { import {
Calendar, Calendar,
ChevronLeft, ChevronLeft,
FileChartColumnIncreasing,
Menu, Menu,
PanelLeft, PanelLeft,
Server, Server,
User, User,
} from "lucide-react"; } from "lucide-react";
import React, { useEffect, useRef, useState } from "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 { Button } from "~/components/ui/button";
import { Calendar as CustomCalendar } from "~/components/ui/Calendar"; import { Calendar as CustomCalendar } from "~/components/ui/Calendar";
import { useAuth } from "~/contexts/auth-context"; import { useAuth } from "~/contexts/auth-context";
import apiService from "~/lib/api"; import apiService from "~/lib/api";
import { cn, EventBus, handleDataValue } from "~/lib/utils"; import { cn, EventBus, handleDataValue } from "~/lib/utils";
interface HeaderProps { interface HeaderProps {
onToggleSidebar?: () => void; onToggleSidebar?: () => void;
className?: string; className?: string;
@ -66,6 +71,115 @@ const monthList: Array<MonthItem> = [
}, },
]; ];
const columns: Array<any> = [
{ 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({ export function Header({
onToggleSidebar, onToggleSidebar,
className, className,
@ -79,6 +193,9 @@ export function Header({
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState<boolean>(false); const [isProfileMenuOpen, setIsProfileMenuOpen] = useState<boolean>(false);
const [isNotificationOpen, setIsNotificationOpen] = useState<boolean>(false); const [isNotificationOpen, setIsNotificationOpen] = useState<boolean>(false);
const [openCalendar, setOpenCalendar] = useState<boolean>(false); const [openCalendar, setOpenCalendar] = useState<boolean>(false);
const [excelLoading, setExcelLoading] = useState<boolean>(false);
const location = useLocation();
const projectManagerRoute = "/dashboard/project-management";
const [currentYear, setCurrentYear] = useState<SelectedDate>({ const [currentYear, setCurrentYear] = useState<SelectedDate>({
since: jy, since: jy,
until: 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<string, any> = {};
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 ( return (
<header <header
className={cn( className={cn(
@ -312,6 +489,20 @@ export function Header({
{/* User Menu */} {/* User Menu */}
<div className="relative"> <div className="relative">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{location.pathname === projectManagerRoute ? (
<div className="flex justify-end w-full mb-0 pl-2">
<span
className={`flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian ${excelLoading ? "!cursor-not-allowed !opacity-10" : ""}`}
onClick={handleDownloadFile}
>
<FileChartColumnIncreasing className="h-4 w-4" />
دانلود فایل اکسل
</span>
</div>
) : (
""
)}
{user?.id === 2041 && ( {user?.id === 2041 && (
<button <button
className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian" className="flex w-full cursor-pointer items-center gap-2 px-3 py-2 text-sm text-gray-300 hover:bg-gradient-to-r hover:from-emerald-500/10 hover:to-teal-500/10 hover:text-emerald-300 font-persian"
@ -341,6 +532,7 @@ export function Header({
</div> </div>
</Button> </Button>
</div> </div>
{/* Profile Dropdown */} {/* Profile Dropdown */}
{isProfileMenuOpen && ( {isProfileMenuOpen && (
<div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50"> <div className="absolute left-0 top-full mt-2 w-48 bg-gray-800 border border-emerald-500/30 rounded-lg shadow-lg z-50">

View File

@ -359,19 +359,6 @@ export function DigitalInnovationPage() {
} }
}, [hasMore, loading, loadingMore]); }, [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(() => { useEffect(() => {
if (date?.start && date?.end) { if (date?.start && date?.end) {
fetchTable(true); fetchTable(true);
@ -381,11 +368,15 @@ export function DigitalInnovationPage() {
}, [sortConfig, date]); }, [sortConfig, date]);
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -361,11 +361,15 @@ export function GreenInnovationPage() {
}; };
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
const loadMore = useCallback(() => { const loadMore = useCallback(() => {

View File

@ -426,11 +426,15 @@ export function InnovationBuiltInsidePage() {
}, [hasMore, loading]); }, [hasMore, loading]);
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -405,11 +405,15 @@ export function ManageIdeasTechPage() {
}, [hasMore, loading, loadingMore]); }, [hasMore, loading, loadingMore]);
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -340,11 +340,15 @@ export function ProcessInnovationPage() {
}, [hasMore, loading]); }, [hasMore, loading]);
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -1,15 +1,9 @@
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import { import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react";
ChevronDown,
ChevronUp,
FileChartColumnIncreasing,
RefreshCw,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import XLSX from "xlsx-js-style"; import XLSX from "xlsx-js-style";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card"; import { Card, CardContent } from "~/components/ui/card";
import { import {
Table, Table,
@ -289,11 +283,15 @@ export function ProjectManagementPage() {
}; };
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
const loadMore = useCallback(() => { const loadMore = useCallback(() => {
if (hasMore && !loading && !loadingMore && !fetchingRef.current) { if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
@ -801,11 +799,13 @@ export function ProjectManagementPage() {
// const totalPages = Math.ceil(totalCount / pageSize); // const totalPages = Math.ceil(totalCount / pageSize);
const exportToExcel = () => { const exportToExcel = async () => {
let arr = []; 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<string, any> = {}; let obj: Record<string, any> = {};
const project = projects[i]; const project = data[i];
Object.entries(project).forEach(([pKey, pValue]) => { Object.entries(project).forEach(([pKey, pValue]) => {
Object.values(columns).forEach((col) => { Object.values(columns).forEach((col) => {
@ -840,10 +840,31 @@ export function ProjectManagementPage() {
saveAs(blob, "people.xls"); 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 ( return (
<DashboardLayout title="مدیریت پروژه‌ها"> <DashboardLayout title="مدیریت پروژه‌ها">
<div className="space-y-6"> <div className="space-y-6">
<div className="flex justify-end w-full mb-0 pl-2"> {/* <div className="flex justify-end w-full mb-0 pl-2">
<Button <Button
className="flex w-max justify-center rounded-xl mb-4 border-gray-500/20 border-2 cursor-pointer transition-all hover:bg-[#3F415A]/50 bg-[#3F415A] py-3 text-center items-center gap-3 " className="flex w-max justify-center rounded-xl mb-4 border-gray-500/20 border-2 cursor-pointer transition-all hover:bg-[#3F415A]/50 bg-[#3F415A] py-3 text-center items-center gap-3 "
variant="ghost" variant="ghost"
@ -853,7 +874,7 @@ export function ProjectManagementPage() {
<FileChartColumnIncreasing /> <FileChartColumnIncreasing />
دانلود فایل اکسل دانلود فایل اکسل
</Button> </Button>
</div> </div> */}
{/* Data Table */} {/* Data Table */}
<Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden"> <Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden">

View File

@ -136,11 +136,15 @@ export function StrategicAlignmentPopup({
}, [open]); }, [open]);
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
const fetchData = async () => { const fetchData = async () => {

View File

@ -68,11 +68,15 @@ export function InfoPanel({ selectedCompany }: InfoPanelProps) {
const [date, setDate] = useStoredDate(); const [date, setDate] = useStoredDate();
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {

View File

@ -78,11 +78,15 @@ export function NetworkGraph({
const [date, setDate] = useStoredDate(); const [date, setDate] = useStoredDate();
useEffect(() => { useEffect(() => {
EventBus.on("dateSelected", (date: CalendarDate) => { const handler = (date: CalendarDate) => {
if (date) { if (date) setDate(date);
setDate(date); };
}
}); EventBus.on("dateSelected", handler);
return () => {
EventBus.off("dateSelected", handler);
};
}, []); }, []);
useEffect(() => { useEffect(() => {