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 { Badge } from "~/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "~/components/ui/table"; import { ChevronUp, ChevronDown, RefreshCw, Calendar, DollarSign, Users, Target, } from "lucide-react"; import apiService from "~/lib/api"; import toast from "react-hot-toast"; interface ProjectData { WorkflowID: number; ValueP1215S1887ValueID: number; ValueP1215S1887StageID: number; project_no: string; title: string; strategic_theme: string; value_technology_and_innovation: string; type_of_innovation: string; innovation: string; person_executing: string; excellent_observer: string; observer: string; moderator: string; start_date: string; end_date: string | null; done_date: string | null; approved_budget: string; budget_spent: string; } interface SortConfig { field: string; direction: "asc" | "desc"; } const columns = [ { key: "project_no", label: "شماره پروژه", sortable: true, width: "120px" }, { key: "title", label: "عنوان پروژه", sortable: true, width: "200px" }, { key: "strategic_theme", label: "ماموریت راهبردی", sortable: true, width: "160px", }, { key: "value_technology_and_innovation", label: "ارزش فناوری و نوآوری", sortable: true, width: "200px", }, { key: "type_of_innovation", label: "انواع نوآوری", sortable: true, width: "140px", }, { key: "innovation", label: "نوآوری", sortable: true, width: "120px", }, { key: "person_executing", label: "مجری", sortable: true, width: "140px", }, { key: "excellent_observer", label: "ناظر عالی", sortable: true, width: "140px", }, { key: "observer", label: "ناظر", sortable: true, width: "140px", }, { key: "moderator", label: "مدیر پروژه", sortable: true, width: "140px", }, { key: "start_date", label: "تاریخ شروع", sortable: true, width: "120px", }, { key: "end_date", label: "تاریخ پایان نهایی", sortable: true, width: "140px", }, { key: "done_date", label: "تاریخ انجام نهایی", sortable: true, width: "140px", }, { key: "approved_budget", label: "بودجه مصوب", sortable: true, width: "150px", }, { key: "budget_spent", label: "بودجه هزینه شده", sortable: true, width: "150px", }, ]; export function ProjectManagementPage() { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [pageSize] = useState(20); const [hasMore, setHasMore] = useState(true); const [totalCount, setTotalCount] = useState(0); const [actualTotalCount, setActualTotalCount] = useState(0); const [sortConfig, setSortConfig] = useState({ field: "start_date", direction: "asc", }); const observerRef = useRef(null); const fetchProjects = async (reset = false) => { try { if (reset) { setLoading(true); setCurrentPage(1); } else { setLoadingMore(true); } const pageToFetch = reset ? 1 : currentPage; const response = await apiService.select({ ProcessName: "project", OutputFields: [ "project_no", "title", "strategic_theme", "value_technology_and_innovation", "type_of_innovation", "innovation", "person_executing", "excellent_observer", "observer", "moderator", "start_date", "end_date", "done_date", "approved_budget", "budget_spent", ], Pagination: { PageNumber: pageToFetch, PageSize: pageSize }, Sorts: [[sortConfig.field, sortConfig.direction]], Conditions: [], }); if (response.state === 0) { // Parse the JSON string from the API response const dataString = response.data; if (dataString && typeof dataString === "string") { try { const parsedData = JSON.parse(dataString); if (Array.isArray(parsedData)) { if (reset) { setProjects(parsedData); setTotalCount(parsedData.length); } else { setProjects((prev) => [...prev, ...parsedData]); setTotalCount((prev) => prev + parsedData.length); } // Check if there are more items to load setHasMore(parsedData.length === pageSize); } else { if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } catch (parseError) { console.error("Error parsing project data:", parseError); if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } else { if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } else { toast.error(response.message || "خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } } catch (error) { console.error("Error fetching projects:", error); toast.error("خطا در دریافت اطلاعات پروژه‌ها"); if (reset) { setProjects([]); setTotalCount(0); } setHasMore(false); } finally { setLoading(false); setLoadingMore(false); } }; const loadMore = useCallback(() => { if (!loadingMore && hasMore) { setCurrentPage((prev) => prev + 1); } }, [loadingMore, hasMore]); useEffect(() => { fetchProjects(true); fetchTotalCount(); }, [sortConfig]); useEffect(() => { if (currentPage > 1) { fetchProjects(false); } }, [currentPage]); // Infinite scroll observer useEffect(() => { const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !loadingMore) { loadMore(); } }, { threshold: 0.1 }, ); if (observerRef.current) { observer.observe(observerRef.current); } return () => { if (observerRef.current) { observer.unobserve(observerRef.current); } }; }, [loadMore, hasMore, loadingMore]); const handleSort = (field: string) => { setSortConfig((prev) => ({ field, direction: prev.field === field && prev.direction === "asc" ? "desc" : "asc", })); setCurrentPage(1); setProjects([]); setHasMore(true); }; const fetchTotalCount = async () => { try { const response = await apiService.select({ ProcessName: "project", OutputFields: ["count(project_no)"], Conditions: [], }); if (response.state === 0) { const dataString = response.data; if (dataString && typeof dataString === "string") { try { const parsedData = JSON.parse(dataString); if (Array.isArray(parsedData) && parsedData[0]) { setActualTotalCount(parsedData[0].project_no_count || 0); } } catch (parseError) { console.error("Error parsing count data:", parseError); } } } } catch (error) { console.error("Error fetching total count:", error); } }; const handleRefresh = () => { setCurrentPage(1); setProjects([]); setHasMore(true); fetchProjects(true); fetchTotalCount(); }; const formatCurrency = (amount: string | number) => { if (!amount) return "0 ریال"; // Remove commas and convert to number const numericAmount = typeof amount === "string" ? parseFloat(amount.replace(/,/g, "")) : amount; if (isNaN(numericAmount)) return "0 ریال"; return new Intl.NumberFormat("fa-IR").format(numericAmount) + " ریال"; }; const formatDate = (dateString: string | null) => { if (!dateString || dateString === "null" || dateString.trim() === "") return "-"; try { return new Intl.DateTimeFormat("fa-IR").format(new Date(dateString)); } catch { return "-"; } }; const renderCellContent = (item: ProjectData, column: any) => { const value = item[column.key as keyof ProjectData]; switch (column.key) { case "approved_budget": case "budget_spent": return ( {formatCurrency(String(value))} ); case "start_date": case "end_date": case "done_date": return ( {formatDate(String(value))} ); case "project_no": return ( {String(value)} ); case "title": return {String(value)}; default: return {String(value) || "-"}; } }; const totalPages = Math.ceil(totalCount / pageSize); return (
{/* Actions */}
{/* Data Table */}
{columns.map((column) => ( {column.sortable ? ( ) : ( column.label )} ))} {loading ? (
در حال بارگذاری...
) : projects.length === 0 ? ( هیچ پروژه‌ای یافت نشد ) : ( projects.map((project, index) => ( {columns.map((column) => ( {renderCellContent(project, column)} ))} )) )}
{/* Infinite scroll trigger */}
{loadingMore && (
در حال بارگذاری...
)} {!hasMore && projects.length > 0 && (
همه داده‌ها نمایش داده شد
)}
{/* Footer */}
نمایش {projects.length} از {actualTotalCount} پروژه کل پروژه‌ها: {actualTotalCount}
); }