mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 05:25:41 +00:00
add file viewer functionality across various components
- Implemented a shared file viewer modal using `useFileViewer` hook to allow inline viewing of documents (images, PDFs, videos, etc.) across the application. - Updated `ContractClearanceReviewSection`, `ContractRequestDetailPage`, `ContractViewPage`, and booking-related components to utilize the new file viewer for document previews. - Added "Approve all" button in `ContractClearanceReviewSection` to bulk approve documents. - Enhanced document action buttons to include view and download options based on file type. - Introduced `isViewable` utility to determine if a file can be previewed inline. - Created `FileViewer` component to handle rendering of various file types and added appropriate fallback for unsupported formats.
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
Download,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
FileCheck2,
|
||||
FileText,
|
||||
MessageSquareWarning,
|
||||
@@ -28,9 +28,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import type { Freight } from "@edr/types";
|
||||
import { isViewable } from "@edr/ui-common";
|
||||
|
||||
import { SectionCard } from "./SectionCard";
|
||||
import { bookingsService } from "@/services/bookings.service";
|
||||
import { useFileViewer } from "@/hooks/useFileViewer";
|
||||
|
||||
export interface ClearanceReviewSectionProps {
|
||||
bookingId: string;
|
||||
@@ -66,6 +68,7 @@ export function ClearanceReviewSection({
|
||||
const [queryNotes, setQueryNotes] = useState<Record<string, string>>({});
|
||||
const [openQuery, setOpenQuery] = useState<Record<string, boolean>>({});
|
||||
const [outputFiles, setOutputFiles] = useState<Record<string, File>>({});
|
||||
const { view, viewer } = useFileViewer();
|
||||
|
||||
const { data: clearance, isLoading } = useQuery({
|
||||
queryKey: ["clearance", bookingId],
|
||||
@@ -207,6 +210,7 @@ export function ClearanceReviewSection({
|
||||
note: queryNotes[doc.fileKey],
|
||||
})
|
||||
}
|
||||
onView={view}
|
||||
busy={reviewMutation.isPending}
|
||||
/>
|
||||
))
|
||||
@@ -233,18 +237,46 @@ export function ClearanceReviewSection({
|
||||
</Group>
|
||||
<Group gap={8} wrap="nowrap">
|
||||
{doc.file ? (
|
||||
<Tooltip label="Download">
|
||||
<Box
|
||||
component="a"
|
||||
href={doc.file.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
c="edr-green"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Download size={15} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<>
|
||||
{isViewable({
|
||||
name: doc.file.name,
|
||||
url: doc.file.url,
|
||||
}) && (
|
||||
<Tooltip label="View">
|
||||
<Box
|
||||
component="button"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
view({
|
||||
name: doc.file!.name,
|
||||
url: doc.file!.url,
|
||||
})
|
||||
}
|
||||
c="edr-green"
|
||||
style={{
|
||||
display: "flex",
|
||||
background: "transparent",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<Eye size={15} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip label="Download">
|
||||
<Box
|
||||
component="a"
|
||||
href={doc.file.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
c="edr-green"
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<Download size={15} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
<Text fz="12px" c="edr-muted">
|
||||
Not uploaded
|
||||
@@ -325,6 +357,7 @@ export function ClearanceReviewSection({
|
||||
</Button>
|
||||
</Group>
|
||||
</Paper>
|
||||
{viewer}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -366,6 +399,7 @@ function DocReviewCard({
|
||||
onNote,
|
||||
onApprove,
|
||||
onQuery,
|
||||
onView,
|
||||
busy,
|
||||
}: {
|
||||
doc: Freight.ClearanceDocument;
|
||||
@@ -375,6 +409,7 @@ function DocReviewCard({
|
||||
onNote: (v: string) => void;
|
||||
onApprove: () => void;
|
||||
onQuery: () => void;
|
||||
onView: (file: { name: string; url: string }) => void;
|
||||
busy: boolean;
|
||||
}) {
|
||||
const status = doc.reviewStatus ?? "PENDING";
|
||||
@@ -420,22 +455,22 @@ function DocReviewCard({
|
||||
<Badge variant="light" color={meta.color} radius="sm">
|
||||
{meta.label}
|
||||
</Badge>
|
||||
{hasFile && (
|
||||
<Tooltip label="Open document">
|
||||
<Button
|
||||
component="a"
|
||||
href={doc.file!.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
size="compact-xs"
|
||||
variant="default"
|
||||
radius="md"
|
||||
leftSection={<ExternalLink size={13} />}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{hasFile &&
|
||||
isViewable({ name: doc.file!.name, url: doc.file!.url }) && (
|
||||
<Tooltip label="Preview document">
|
||||
<Button
|
||||
size="compact-xs"
|
||||
variant="default"
|
||||
radius="md"
|
||||
leftSection={<Eye size={13} />}
|
||||
onClick={() =>
|
||||
onView({ name: doc.file!.name, url: doc.file!.url })
|
||||
}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user