ui on new contrat page

This commit is contained in:
Marshal
2026-06-28 23:39:35 +00:00
parent 47c91cad03
commit 947dcbed3d
6 changed files with 647 additions and 134 deletions

View File

@@ -1,8 +1,10 @@
import { useState } from "react";
import { Button, FileButton, Group, Select, Stack, Text } from "@mantine/core";
import { FileUp, Upload } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { Box, Button, FileButton, Group, Select, Stack, Text } from "@mantine/core";
import { Eye, FileText, FileUp, Upload } from "lucide-react";
import { isViewable } from "@edr/ui-common";
import { useUploadGlDocuments } from "@/hooks/contracts/useContracts";
import { useFileViewer } from "@/hooks/useFileViewer";
import { ActionShell } from "./ActionShell";
/**
@@ -23,6 +25,7 @@ export function GlDocumentUploadCard({ bookingId }: { bookingId: string }) {
const upload = useUploadGlDocuments(bookingId);
const [slot, setSlot] = useState<string | null>(GL_DOC_SLOTS[0].value);
const [file, setFile] = useState<File | null>(null);
const { view, viewer } = useFileViewer();
const submit = () => {
if (!slot || !file) return;
@@ -69,12 +72,108 @@ export function GlDocumentUploadCard({ bookingId }: { bookingId: string }) {
Upload
</Button>
</Group>
{!file ? (
{file ? (
<StagedFilePreview file={file} onPreview={view} />
) : (
<Text size="xs" c="dimmed">
PDF or image. The matching milestone completes on upload.
</Text>
) : null}
)}
</Stack>
{viewer}
</ActionShell>
);
}
/**
* A compact preview chip for the GL file staged for upload: an image thumbnail
* (or a glyph) and a Preview button that opens the file in the shared viewer via
* a local object URL (minted once, revoked on unmount).
*/
function StagedFilePreview({
file,
onPreview,
}: {
file: File;
onPreview: (f: { name: string; url: string; mimeType?: string | null }) => void;
}) {
const url = useMemo(() => URL.createObjectURL(file), [file]);
useEffect(() => () => URL.revokeObjectURL(url), [url]);
const isImage =
file.type.startsWith("image/") ||
["png", "jpg", "jpeg", "webp", "gif", "bmp", "svg"].includes(
file.name.split(".").pop()?.toLowerCase() ?? "",
);
const canPreview = isViewable({ name: file.name, url, mimeType: file.type });
return (
<Group
gap={10}
wrap="nowrap"
p={8}
style={{
borderRadius: 10,
border: "1px dashed var(--mantine-color-edr-green-5)",
background: "var(--mantine-color-edr-green-0)",
minWidth: 0,
}}
>
{isImage ? (
<Box
onClick={() => onPreview({ name: file.name, url, mimeType: file.type })}
style={{
width: 38,
height: 38,
flexShrink: 0,
borderRadius: 8,
overflow: "hidden",
cursor: "pointer",
border: "1px solid var(--mantine-color-gray-3)",
}}
>
<img
src={url}
alt=""
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
</Box>
) : (
<Box
c="edr-green"
style={{
width: 38,
height: 38,
flexShrink: 0,
borderRadius: 8,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "var(--mantine-color-edr-green-1)",
}}
>
<FileText size={17} />
</Box>
)}
<Box style={{ minWidth: 0, flex: 1 }}>
<Text size="xs" fw={700} c="edr-green">
Ready to upload
</Text>
<Text size="xs" truncate>
{file.name}
</Text>
</Box>
{canPreview && (
<Button
size="compact-xs"
variant="subtle"
color="edr-green"
leftSection={<Eye size={13} />}
onClick={() => onPreview({ name: file.name, url, mimeType: file.type })}
>
Preview
</Button>
)}
</Group>
);
}