Add interactive progress bar with slide preview to reports

Implement a slider component for report navigation, enabling direct seeking and hover-based slide previews, replacing previous/next buttons.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9a264234-c5d7-4dcc-adf3-a954b149b30d
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/9a264234-c5d7-4dcc-adf3-a954b149b30d/VGhYqEL
This commit is contained in:
kimjaehyeon0101
2025-10-14 07:14:21 +00:00
parent 4b5dcde3ec
commit 47031a80cd

View File

@ -5,6 +5,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { Slider } from "@/components/ui/slider";
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/useAuth";
import { useState, useEffect, useRef, useMemo } from "react";
@ -35,6 +36,8 @@ function MohamedSalahSlides() {
const [currentSlide, setCurrentSlide] = useState(1);
const [numPages, setNumPages] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [hoverSlide, setHoverSlide] = useState<number | null>(null);
const [hoverPosition, setHoverPosition] = useState({ x: 0, y: 0 });
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
setNumPages(numPages);
@ -47,6 +50,28 @@ function MohamedSalahSlides() {
cMapPacked: true,
}), []);
const handleSliderChange = (value: number[]) => {
if (numPages > 0) {
setCurrentSlide(value[0]);
}
};
const handleSliderHover = (e: React.MouseEvent<HTMLDivElement>) => {
if (numPages === 0) return;
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = x / rect.width;
const slideNumber = Math.max(1, Math.min(numPages, Math.round(percentage * numPages)));
setHoverSlide(slideNumber);
setHoverPosition({ x: e.clientX, y: rect.top - 60 });
};
const handleSliderLeave = () => {
setHoverSlide(null);
};
return (
<div className="space-y-6">
<div className="w-full relative overflow-hidden" style={{ paddingTop: '56.25%' }} data-testid="slide-container-16-9">
@ -77,6 +102,59 @@ function MohamedSalahSlides() {
</Card>
</div>
<div className="space-y-4">
{/* Progress Bar */}
<div className="relative px-2" data-testid="slide-progress-container">
<div
className="relative py-2"
onMouseMove={handleSliderHover}
onMouseLeave={handleSliderLeave}
data-testid="slider-hover-area"
>
<Slider
value={[currentSlide]}
onValueChange={handleSliderChange}
min={1}
max={numPages || 1}
step={1}
disabled={numPages === 0}
className="cursor-pointer"
data-testid="slider-progress"
/>
{/* Slide markers */}
<div className="absolute top-0 left-0 right-0 h-full pointer-events-none">
<div className="relative h-full px-2">
{numPages > 0 && Array.from({ length: numPages }).map((_, i) => (
<div
key={i}
className="absolute top-1/2 -translate-y-1/2 w-1 h-1 bg-gray-300 rounded-full"
style={{
left: `${(i / (numPages - 1)) * 100}%`,
transform: 'translate(-50%, -50%)'
}}
/>
))}
</div>
</div>
</div>
{/* Hover tooltip */}
{hoverSlide !== null && (
<div
className="fixed z-50 bg-gray-900 text-white px-3 py-2 rounded-lg text-sm font-medium shadow-lg pointer-events-none"
style={{
left: `${hoverPosition.x}px`,
top: `${hoverPosition.y}px`,
transform: 'translateX(-50%)'
}}
data-testid="tooltip-slide-preview"
>
Slide {hoverSlide}
</div>
)}
</div>
{/* Control buttons */}
<div className="flex items-center justify-between">
<Button
variant="outline"
@ -99,6 +177,7 @@ function MohamedSalahSlides() {
</Button>
</div>
</div>
</div>
);
}