Compare commits

...

1 Commits

Author SHA1 Message Date
31fa601ab2 fix: fix the scroll infinite in this version 2025-09-24 14:08:34 +03:30
6 changed files with 91 additions and 48 deletions

View File

@ -230,7 +230,7 @@ export function DashboardHome() {
style={{ height: `${Math.random() * 80 + 20}%` }} style={{ height: `${Math.random() * 80 + 20}%` }}
></div> ></div>
<div <div
className="w-full bg-green-400/30 rounded-t-sm" className="w-full bg-pr-green rounded-t-sm"
style={{ height: `${Math.random() * 80 + 20}%` }} style={{ height: `${Math.random() * 80 + 20}%` }}
></div> ></div>
<div <div
@ -378,7 +378,7 @@ export function DashboardHome() {
gridType="circle" gridType="circle"
radialLines={false} radialLines={false}
stroke="none" stroke="none"
className="first:fill-red-400 last:fill-[#24273A]" className="first:fill-pr-red last:fill-[#24273A]"
polarRadius={[38, 31]} polarRadius={[38, 31]}
/> />
<RadialBar <RadialBar
@ -506,7 +506,7 @@ export function DashboardHome() {
gridType="circle" gridType="circle"
radialLines={false} radialLines={false}
stroke="none" stroke="none"
className="first:fill-red-400 last:fill-[#24273A]" className="first:fill-pr-red last:fill-[#24273A]"
polarRadius={[38, 31]} polarRadius={[38, 31]}
/> />
<RadialBar <RadialBar
@ -734,7 +734,7 @@ export function DashboardHome() {
</div> </div>
<div className="flex items-center justify-center gap-4"> <div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Book className="w-4 h-4 text-green-400" /> <Book className="w-4 h-4 text-pr-green" />
<span className="text-sm">مقاله:</span> <span className="text-sm">مقاله:</span>
</div> </div>
<span className="text-base font-bold "> <span className="text-base font-bold ">
@ -791,7 +791,7 @@ export function DashboardHome() {
</div> </div>
<div className="flex items-center justify-center gap-4"> <div className="flex items-center justify-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Book className="w-4 h-4 text-green-400" /> <Book className="w-4 h-4 text-pr-green" />
<span className="text-sm">برگزاری رویداد:</span> <span className="text-sm">برگزاری رویداد:</span>
</div> </div>
<span className="text-base font-bold "> <span className="text-base font-bold ">

View File

@ -181,6 +181,8 @@ export function DigitalInnovationPage() {
// const [avarage, setAvarage] = useState<number>(0); // const [avarage, setAvarage] = useState<number>(0);
const observerRef = useRef<HTMLDivElement>(null); const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false); const fetchingRef = useRef(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
// Selection handlers // Selection handlers
const handleSelectAll = () => { const handleSelectAll = () => {
@ -346,10 +348,10 @@ export function DigitalInnovationPage() {
}; };
const loadMore = useCallback(() => { const loadMore = useCallback(() => {
if (hasMore && !loading) { if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
setCurrentPage((prev) => prev + 1); setCurrentPage((prev) => prev + 1);
} }
}, [hasMore, loading]); }, [hasMore, loading, loadingMore]);
useEffect(() => { useEffect(() => {
fetchTable(true); fetchTable(true);
@ -363,28 +365,41 @@ export function DigitalInnovationPage() {
} }
}, [currentPage]); }, [currentPage]);
// Infinite scroll observer with debouncing
useEffect(() => { useEffect(() => {
const scrollContainer = document.querySelector(".overflow-auto"); const scrollContainer = scrollContainerRef.current;
const handleScroll = () => { const handleScroll = () => {
if (!scrollContainer || !hasMore) return; if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollContainer; // Clear previous timeout
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
if (scrollPercentage == 1) {
loadMore();
} }
// Debounce scroll events
scrollTimeoutRef.current = setTimeout(() => {
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
// Trigger load more when scrolled to 95% of the container
if (scrollPercentage >= 0.95) {
loadMore();
}
}, 150);
}; };
if (scrollContainer) { if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll); scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
} }
return () => { return () => {
if (scrollContainer) { if (scrollContainer) {
scrollContainer.removeEventListener("scroll", handleScroll); scrollContainer.removeEventListener("scroll", handleScroll);
} }
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
}; };
}, [loadMore, hasMore, loadingMore]); }, [loadMore, hasMore, loadingMore]);

View File

@ -244,6 +244,8 @@ export function ManageIdeasTechPage() {
}); });
const observerRef = useRef<HTMLDivElement>(null); const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false); const fetchingRef = useRef(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const fetchProjects = async (reset = false) => { const fetchProjects = async (reset = false) => {
// Prevent concurrent API calls // Prevent concurrent API calls
@ -341,10 +343,10 @@ export function ManageIdeasTechPage() {
}; };
const loadMore = useCallback(() => { const loadMore = useCallback(() => {
if (hasMore && !loading) { if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
setCurrentPage((prev) => prev + 1); setCurrentPage((prev) => prev + 1);
} }
}, [hasMore, loading]); }, [hasMore, loading, loadingMore]);
useEffect(() => { useEffect(() => {
fetchProjects(true); fetchProjects(true);
@ -357,30 +359,41 @@ export function ManageIdeasTechPage() {
} }
}, [currentPage]); }, [currentPage]);
// Infinite scroll observer // Infinite scroll observer with debouncing
useEffect(() => { useEffect(() => {
const scrollContainer = document.querySelector(".overflow-auto"); const scrollContainer = scrollContainerRef.current;
const handleScroll = () => { const handleScroll = () => {
if (!scrollContainer || !hasMore) return; if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollContainer; // Clear previous timeout
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
// Trigger load more when scrolled to 90% of the container
if (scrollPercentage == 1) {
loadMore();
} }
// Debounce scroll events
scrollTimeoutRef.current = setTimeout(() => {
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
// Trigger load more when scrolled to 95% of the container
if (scrollPercentage >= 0.95) {
loadMore();
}
}, 150);
}; };
if (scrollContainer) { if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll); scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
} }
return () => { return () => {
if (scrollContainer) { if (scrollContainer) {
scrollContainer.removeEventListener("scroll", handleScroll); scrollContainer.removeEventListener("scroll", handleScroll);
} }
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
}; };
}, [loadMore, hasMore, loadingMore]); }, [loadMore, hasMore, loadingMore]);
@ -656,7 +669,7 @@ export function ManageIdeasTechPage() {
<Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden"> <Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden">
<CardContent className="p-0"> <CardContent className="p-0">
<div className="relative"> <div className="relative">
<Table containerClassName="overflow-auto custom-scrollbar max-h-[calc(100vh-200px)]"> <Table containerRef={scrollContainerRef} containerClassName="overflow-auto custom-scrollbar max-h-[calc(100vh-200px)]">
<TableHeader className="sticky top-0 z-50 bg-muted"> <TableHeader className="sticky top-0 z-50 bg-muted">
<TableRow className="bg-muted"> <TableRow className="bg-muted">
{columns.map((column) => ( {columns.map((column) => (

View File

@ -167,6 +167,8 @@ export function ProjectManagementPage() {
}); });
const observerRef = useRef<HTMLDivElement>(null); const observerRef = useRef<HTMLDivElement>(null);
const fetchingRef = useRef(false); const fetchingRef = useRef(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const fetchProjects = async (reset = false) => { const fetchProjects = async (reset = false) => {
// Prevent concurrent API calls // Prevent concurrent API calls
@ -264,10 +266,10 @@ export function ProjectManagementPage() {
}; };
const loadMore = useCallback(() => { const loadMore = useCallback(() => {
if (hasMore && !loading) { if (hasMore && !loading && !loadingMore && !fetchingRef.current) {
setCurrentPage((prev) => prev + 1); setCurrentPage((prev) => prev + 1);
} }
}, [hasMore, loading]); }, [hasMore, loading, loadingMore]);
useEffect(() => { useEffect(() => {
fetchProjects(true); fetchProjects(true);
@ -280,29 +282,43 @@ export function ProjectManagementPage() {
} }
}, [currentPage]); }, [currentPage]);
// Infinite scroll observer // Infinite scroll observer with debouncing
useEffect(() => { useEffect(() => {
const scrollContainer = document.querySelector(".overflow-auto"); const scrollContainer = scrollContainerRef.current;
const handleScroll = () => {
if (!scrollContainer || !hasMore) return;
const { scrollTop, scrollHeight, clientHeight } = scrollContainer; const handleScroll = () => {
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; if (!scrollContainer || !hasMore || loadingMore || fetchingRef.current) return;
// Trigger load more when scrolled to 90% of the container
if (scrollPercentage == 1 || scrollPercentage == .9) { // Clear previous timeout
loadMore(); if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
} }
// Debounce scroll events
scrollTimeoutRef.current = setTimeout(() => {
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
const scrollPercentage = (scrollTop + clientHeight) / scrollHeight;
// Trigger load more when scrolled to 95% of the container
if (scrollPercentage >= 0.95) {
loadMore();
}
}, 150);
}; };
if (scrollContainer) { if (scrollContainer) {
scrollContainer.addEventListener("scroll", handleScroll); scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
} }
return () => { return () => {
if (scrollContainer) { if (scrollContainer) {
scrollContainer.removeEventListener("scroll", handleScroll); scrollContainer.removeEventListener("scroll", handleScroll);
} }
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
}; };
}, [loadMore, hasMore]); }, [loadMore, hasMore, loadingMore]);
const handleSort = (field: string) => { const handleSort = (field: string) => {
fetchingRef.current = false; // Reset fetching state on sort fetchingRef.current = false; // Reset fetching state on sort
@ -754,7 +770,7 @@ export function ProjectManagementPage() {
<Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden"> <Card className="bg-transparent backdrop-blur-sm rounded-2xl overflow-hidden">
<CardContent className="p-0"> <CardContent className="p-0">
<div className="relative"> <div className="relative">
<div className="relative overflow-auto custom-scrollbar max-h-[calc(100vh-120px)]"> <div ref={scrollContainerRef} className="relative overflow-auto custom-scrollbar max-h-[calc(100vh-120px)]">
<Table className="table-fixed"> <Table className="table-fixed">
<TableHeader className="sticky top-0 z-50 bg-[#3F415A]"> <TableHeader className="sticky top-0 z-50 bg-[#3F415A]">
<TableRow className="bg-[#3F415A]"> <TableRow className="bg-[#3F415A]">

View File

@ -16,13 +16,11 @@ const Progress = React.forwardRef<
{...props} {...props}
> >
<span className="left-0 text-sm absolute z-10 px-2 text-[#5F6284]">۰%</span> <span className="left-0 text-sm absolute z-10 px-2 text-[#5F6284]">۰%</span>
<span className="w-full text-sm absolute z-10 px-2 text-[#5F6284]" <span className="w-full right-0 text-sm absolute z-10 px-2 text-[#5F6284]"
style={{ transform: `translateX(-${10 - (value || 0)}%)` }}
>{formatNumber(Math.ceil(value || 0 * 10) / 10)}%</span> >{formatNumber(Math.ceil(value || 0 * 10) / 10)}%</span>
<span className="right-0 text-sm absolute z-10 px-2 text-[#5F6284]">{formatNumber(.2)}%</span>
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all" className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${20 - (value || 0)}%)` }} style={{ transform: `translateX(-${15 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
)) ))

View File

@ -4,11 +4,12 @@ import { cn } from "~/lib/utils"
interface TableProps extends React.HTMLAttributes<HTMLTableElement> { interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
containerClassName?: string containerClassName?: string
containerRef?: React.RefObject<HTMLDivElement>
} }
const Table = React.forwardRef<HTMLTableElement, TableProps>( const Table = React.forwardRef<HTMLTableElement, TableProps>(
({ className, containerClassName, ...props }, ref) => ( ({ className, containerClassName, containerRef, ...props }, ref) => (
<div className={cn("relative w-full", containerClassName)}> <div ref={containerRef} className={cn("relative w-full", containerClassName)}>
<table <table
ref={ref} ref={ref}
className={cn("w-full caption-bottom text-sm h-full", className)} className={cn("w-full caption-bottom text-sm h-full", className)}