Reverse feed direction and add navigation arrows
This commit is contained in:
@@ -18,11 +18,14 @@ import {
|
|||||||
Activity,
|
Activity,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
FileText,
|
FileText,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { EventDialog } from "./EventFeed.jsx";
|
import { EventDialog } from "./EventFeed.jsx";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
const METRIC_IDS = {
|
const METRIC_IDS = {
|
||||||
PLACED_ORDER: "Y8cqcF",
|
PLACED_ORDER: "Y8cqcF",
|
||||||
@@ -319,6 +322,34 @@ const MiniEventFeed = ({
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [lastUpdate, setLastUpdate] = useState(null);
|
const [lastUpdate, setLastUpdate] = useState(null);
|
||||||
const scrollRef = useRef(null);
|
const scrollRef = useRef(null);
|
||||||
|
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||||
|
const [showRightArrow, setShowRightArrow] = useState(false);
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current;
|
||||||
|
setShowLeftArrow(scrollLeft > 0);
|
||||||
|
setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToEnd = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTo({
|
||||||
|
left: scrollRef.current.scrollWidth,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollToStart = () => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
scrollRef.current.scrollTo({
|
||||||
|
left: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchEvents = useCallback(async () => {
|
const fetchEvents = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -354,6 +385,7 @@ const MiniEventFeed = ({
|
|||||||
left: scrollRef.current.scrollWidth,
|
left: scrollRef.current.scrollWidth,
|
||||||
behavior: 'instant'
|
behavior: 'instant'
|
||||||
});
|
});
|
||||||
|
handleScroll();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -370,37 +402,63 @@ const MiniEventFeed = ({
|
|||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [fetchEvents]);
|
}, [fetchEvents]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleScroll();
|
||||||
|
}, [events]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed bottom-0 left-0 right-0">
|
<div className="fixed bottom-0 left-0 right-0">
|
||||||
<Card className="bg-gradient-to-br rounded-none from-gray-900 to-gray-600 backdrop-blur">
|
<Card className="bg-gradient-to-br rounded-none from-gray-900 to-gray-600 backdrop-blur">
|
||||||
<div className="px-1 pt-2 pb-3">
|
<div className="px-1 pt-2 pb-3 relative">
|
||||||
<div className="overflow-x-auto overflow-y-hidden [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']">
|
{showLeftArrow && (
|
||||||
<div className="flex flex-row gap-3 pr-4" style={{ width: 'max-content' }}>
|
<Button
|
||||||
{loading && !events.length ? (
|
variant="ghost"
|
||||||
<LoadingState />
|
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 bg-gray-900/50 hover:bg-gray-900/75 h-12 w-8 p-0 [&_svg]:!h-8 [&_svg]:!w-8"
|
||||||
) : error ? (
|
onClick={scrollToStart}
|
||||||
<Alert variant="destructive" className="mx-4">
|
>
|
||||||
<AlertCircle className="h-4 w-4" />
|
<ChevronLeft className="text-white" />
|
||||||
<AlertTitle>Error</AlertTitle>
|
</Button>
|
||||||
<AlertDescription>
|
)}
|
||||||
Failed to load event feed: {error}
|
{showRightArrow && (
|
||||||
</AlertDescription>
|
<Button
|
||||||
</Alert>
|
variant="ghost"
|
||||||
) : !events || events.length === 0 ? (
|
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 bg-gray-900/50 hover:bg-gray-900/75 h-12 w-8 p-0 [&_svg]:!h-8 [&_svg]:!w-8"
|
||||||
<div className="px-4">
|
onClick={scrollToEnd}
|
||||||
<EmptyState />
|
>
|
||||||
</div>
|
<ChevronRight className="text-white" />
|
||||||
) : (
|
</Button>
|
||||||
events.map((event) => (
|
)}
|
||||||
<EventCard
|
<div
|
||||||
key={event.id}
|
ref={scrollRef}
|
||||||
event={event}
|
onScroll={handleScroll}
|
||||||
/>
|
className="overflow-x-auto overflow-y-hidden [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']"
|
||||||
))
|
>
|
||||||
)}
|
<div className="flex flex-row gap-3 pr-4" style={{ width: 'max-content' }}>
|
||||||
|
{loading && !events.length ? (
|
||||||
|
<LoadingState />
|
||||||
|
) : error ? (
|
||||||
|
<Alert variant="destructive" className="mx-4">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Failed to load event feed: {error}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
) : !events || events.length === 0 ? (
|
||||||
|
<div className="px-4">
|
||||||
|
<EmptyState />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
[...events].reverse().map((event) => (
|
||||||
|
<EventCard
|
||||||
|
key={event.id}
|
||||||
|
event={event}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user