Rocky_Mountain_Vending/components/manual-viewer.tsx

330 lines
12 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import {
X,
Download,
ExternalLink,
AlertCircle,
ShoppingCart,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogHeader,
DialogDescription,
DialogTitle,
DialogClose,
} from "@/components/ui/dialog"
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"
import { PartsPanel } from "@/components/parts-panel"
import { useIsMobile } from "@/hooks/use-mobile"
interface ManualViewerProps {
manualUrl: string
filename: string
isOpen: boolean
onClose: () => void
}
export function ManualViewer({
manualUrl,
filename,
isOpen,
onClose,
}: ManualViewerProps) {
const [pdfError, setPdfError] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [showPartsPanel, setShowPartsPanel] = useState(true)
const [partsPanelLoading, setPartsPanelLoading] = useState(true)
const [partsPanelVisible, setPartsPanelVisible] = useState(true)
const isMobile = useIsMobile()
// Reset error state when manual URL changes
useEffect(() => {
if (isOpen) {
setPdfError(false)
setIsLoading(true)
setPartsPanelLoading(true)
setPartsPanelVisible(true)
}
}, [manualUrl, isOpen])
// The URL is already properly encoded in getManualUrl function
// Just use it as-is for the iframe src
const encodedUrl = manualUrl
const handleIframeLoad = () => {
setIsLoading(false)
}
const handleIframeError = () => {
setIsLoading(false)
setPdfError(true)
}
const showPartsPanelWithData =
showPartsPanel && (partsPanelLoading || partsPanelVisible)
const canToggleParts = partsPanelLoading || partsPanelVisible
const partsToggleLabel = partsPanelLoading
? "Checking Parts..."
: partsPanelVisible
? showPartsPanel
? "Hide Parts"
: "Show Parts"
: "Parts Unavailable"
// Mobile layout - use Sheet
if (isMobile) {
return (
<Sheet open={isOpen} onOpenChange={onClose}>
<SheetContent
side="bottom"
className="h-screen max-h-screen flex flex-col p-0 gap-0 [&>button]:hidden"
>
<SheetHeader className="px-4 pt-4 pb-3 border-b flex-shrink-0">
<div className="flex items-center justify-between gap-2">
<SheetTitle className="text-base font-semibold line-clamp-1 flex-1 min-w-0">
{filename.replace(/\.pdf$/i, "")}
</SheetTitle>
<div className="flex items-center gap-1 flex-shrink-0">
<Button
variant="outline"
size="sm"
onClick={() => {
const link = document.createElement("a")
link.href = encodedUrl
link.download = filename
link.click()
}}
className="h-8 px-2"
>
<Download className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="h-8 px-2"
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
</SheetHeader>
<div className="flex-1 overflow-hidden min-h-0 relative bg-muted/50">
{isLoading && !pdfError && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">
Loading PDF...
</p>
</div>
</div>
)}
{pdfError ? (
<div className="absolute inset-0 flex items-center justify-center bg-background z-10 p-4">
<div className="text-center max-w-md">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">
Unable to Load PDF
</h3>
<p className="text-sm text-muted-foreground mb-4">
The PDF file could not be loaded. This may be due to file
corruption or an encoding issue.
</p>
<div className="flex flex-col gap-2">
<Button
variant="outline"
onClick={() => window.open(encodedUrl, "_blank")}
className="w-full"
>
<ExternalLink className="h-4 w-4 mr-2" />
Try Opening in New Tab
</Button>
<Button
variant="outline"
onClick={() => {
const link = document.createElement("a")
link.href = encodedUrl
link.download = filename
link.click()
}}
className="w-full"
>
<Download className="h-4 w-4 mr-2" />
Download Instead
</Button>
</div>
</div>
</div>
) : (
<iframe
src={`${encodedUrl}#toolbar=1&navpanes=0&scrollbar=1`}
className="w-full h-full border-0 min-h-full block"
title={filename}
onLoad={handleIframeLoad}
onError={handleIframeError}
/>
)}
</div>
</SheetContent>
</Sheet>
)
}
// Desktop layout - use Dialog
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
className="!max-w-none !w-screen !h-screen !max-h-screen p-0 flex flex-col gap-0 !m-0 !rounded-none !border-0 !translate-x-0 !translate-y-0 !top-0 !left-0 !right-0 !bottom-0 !inset-0"
showCloseButton={false}
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
width: "100vw",
height: "100vh",
maxWidth: "100vw",
maxHeight: "100vh",
transform: "none",
margin: 0,
borderRadius: 0,
border: "none",
}}
>
<DialogHeader className="px-4 sm:px-6 pt-4 sm:pt-6 pb-3 sm:pb-4 border-b flex-shrink-0 bg-background">
<DialogDescription className="sr-only">
PDF viewer for {filename.replace(/\.pdf$/i, "")}. Use the actions to
open the manual in a new tab, download it, or browse available
parts.
</DialogDescription>
<div className="flex items-center justify-between gap-4">
<DialogTitle className="text-base sm:text-lg font-semibold line-clamp-1 flex-1 min-w-0">
{filename.replace(/\.pdf$/i, "")}
</DialogTitle>
<div className="flex items-center gap-2 flex-shrink-0">
<Button
variant="outline"
size="sm"
onClick={() => setShowPartsPanel(!showPartsPanel)}
disabled={!canToggleParts}
>
<ShoppingCart className="h-4 w-4 mr-1" />
{partsToggleLabel}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => window.open(encodedUrl, "_blank")}
>
<ExternalLink className="h-4 w-4 mr-1" />
Open in New Tab
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
const link = document.createElement("a")
link.href = encodedUrl
link.download = filename
link.click()
}}
>
<Download className="h-4 w-4 mr-1" />
Download
</Button>
<DialogClose asChild>
<Button variant="ghost" size="sm" className="flex-shrink-0">
<X className="h-4 w-4" />
</Button>
</DialogClose>
</div>
</div>
</DialogHeader>
<div className="flex-1 overflow-hidden min-h-0 relative bg-muted/50 flex flex-row w-full">
{/* PDF Viewer - responsive width based on parts panel */}
<div
className={`overflow-hidden min-h-0 relative transition-all duration-300 h-full ${
showPartsPanelWithData ? "w-[75%] lg:w-[80%]" : "w-full"
}`}
>
{isLoading && !pdfError && (
<div className="absolute inset-0 flex items-center justify-center bg-background/80 z-10">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">
Loading PDF...
</p>
</div>
</div>
)}
{pdfError ? (
<div className="absolute inset-0 flex items-center justify-center bg-background z-10">
<div className="text-center p-6 max-w-md">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">
Unable to Load PDF
</h3>
<p className="text-sm text-muted-foreground mb-4">
The PDF file could not be loaded. This may be due to file
corruption or an encoding issue.
</p>
<div className="flex gap-2 justify-center">
<Button
variant="outline"
onClick={() => window.open(encodedUrl, "_blank")}
>
<ExternalLink className="h-4 w-4 mr-2" />
Try Opening in New Tab
</Button>
<Button
variant="outline"
onClick={() => {
const link = document.createElement("a")
link.href = encodedUrl
link.download = filename
link.click()
}}
>
<Download className="h-4 w-4 mr-2" />
Download Instead
</Button>
</div>
</div>
</div>
) : (
<iframe
src={`${encodedUrl}#toolbar=1&navpanes=0&scrollbar=1`}
className="w-full h-full border-0 min-h-full block"
title={filename}
onLoad={handleIframeLoad}
onError={handleIframeError}
/>
)}
</div>
{/* Parts Panel - right side, responsive width */}
{showPartsPanelWithData && (
<PartsPanel
manualFilename={filename}
className={`border-l border-yellow-300/20 bg-yellow-50 dark:bg-yellow-950/90 overflow-y-auto h-full ${"w-[25%] lg:w-[20%]"}`}
onStateChange={(state) => {
setPartsPanelLoading(state.isLoading)
setPartsPanelVisible(state.isVisible)
}}
/>
)}
</div>
</DialogContent>
</Dialog>
)
}