preview pdf

This commit is contained in:
phaichayon
2026-07-14 18:37:09 +07:00
parent a9dcc11ea9
commit b51bf3e02a
6 changed files with 557 additions and 249 deletions

View File

@@ -2,7 +2,7 @@
import Link from "next/link";
import { useMemo, useState } from "react";
import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import { AlertModal } from "@/components/modal/alert-modal";
import { Icons } from "@/components/icons";
@@ -76,6 +76,7 @@ import {
import {
downloadApprovedQuotationPdf,
downloadQuotationCustomerPackage,
generateApprovedQuotationPdf,
} from "../api/service";
import type {
QuotationAttachmentMutationPayload,
@@ -814,7 +815,8 @@ export function QuotationDetail({
canDownloadCustomerPackage: boolean;
canGenerateApprovedPdf: boolean;
}) {
const { data } = useSuspenseQuery(quotationByIdOptions(quotationId));
const queryClient = useQueryClient();
const { data } = useSuspenseQuery(quotationByIdOptions(quotationId));
const { data: itemsData } = useSuspenseQuery(
quotationItemsOptions(quotationId),
);
@@ -1064,21 +1066,39 @@ export function QuotationDetail({
toast.error(message);
},
});
const downloadApprovedPdf = useMutation({
mutationFn: async () => downloadApprovedQuotationPdf(quotationId),
onSuccess: ({ blob, fileName }) => {
downloadBlobFile(blob, fileName);
toast.success("Approved PDF downloaded successfully");
const downloadApprovedPdf = useMutation({
mutationFn: async () => downloadApprovedQuotationPdf(quotationId),
onSuccess: ({ blob, fileName }) => {
downloadBlobFile(blob, fileName);
toast.success("Approved PDF downloaded successfully");
},
onError: (error) =>
toast.error(
error instanceof Error
? error.message
: "Failed to download approved PDF",
),
});
error instanceof Error
? error.message
: "Failed to download approved PDF",
),
});
const createApprovedPdf = useMutation({
mutationFn: async () => generateApprovedQuotationPdf(quotationId),
onSuccess: async ({ blob, fileName }) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: quotationKeys.detail(quotationId) }),
queryClient.invalidateQueries({ queryKey: quotationKeys.lists() }),
queryClient.invalidateQueries({ queryKey: quotationDocumentKeys.data(quotationId) }),
queryClient.invalidateQueries({ queryKey: quotationDocumentKeys.preview(quotationId) }),
]);
setCustomerPackageError(null);
downloadBlobFile(blob, fileName);
toast.success("Approved PDF generated successfully");
},
onError: (error) =>
toast.error(
error instanceof Error ? error.message : "Failed to generate approved PDF",
),
});
const branchMap = useMemo(
const branchMap = useMemo(
() => new Map(referenceData.branches.map((item) => [item.id, item.name])),
[referenceData.branches],
);
@@ -1134,7 +1154,7 @@ export function QuotationDetail({
? customerPackageError
: hasOfficialDocument
? "Generate the package from the approved PDF and active document library files."
: "Customer package requires an approved PDF before it can be generated.";
: "Customer package requires an approved PDF before it can be generated. Generate the official PDF first.";
const canReviseByStatus = [
"accepted",
"rejected",
@@ -2044,13 +2064,24 @@ export function QuotationDetail({
),
)}
</div>
<div className="flex flex-wrap gap-2">
{canGenerateCustomerPackage ? (
<Button
variant="outline"
isLoading={generateCustomerPackage.isPending}
onClick={() =>
customerPackage
<div className="flex flex-wrap gap-2">
{canGenerateApprovedPdf && !hasOfficialDocument ? (
<Button
variant="outline"
isLoading={createApprovedPdf.isPending}
onClick={() => createApprovedPdf.mutate()}
>
<Icons.badgeCheck className="mr-2 h-4 w-4" />
Generate Official PDF
</Button>
) : null}
{canGenerateCustomerPackage ? (
<Button
variant="outline"
disabled={!hasOfficialDocument}
isLoading={generateCustomerPackage.isPending}
onClick={() =>
customerPackage
? setConfirmRegenerateOpen(true)
: generateCustomerPackage.mutate({
quotationId,
@@ -2134,13 +2165,24 @@ export function QuotationDetail({
customer-deliverable package.
</CardDescription>
</div>
<div className="flex flex-wrap gap-2">
{canGenerateCustomerPackage ? (
<Button
variant="outline"
isLoading={generateCustomerPackage.isPending}
onClick={() =>
customerPackage
<div className="flex flex-wrap gap-2">
{canGenerateApprovedPdf && !hasOfficialDocument ? (
<Button
variant="outline"
isLoading={createApprovedPdf.isPending}
onClick={() => createApprovedPdf.mutate()}
>
<Icons.badgeCheck className="mr-2 h-4 w-4" />
Generate Official PDF
</Button>
) : null}
{canGenerateCustomerPackage ? (
<Button
variant="outline"
disabled={!hasOfficialDocument}
isLoading={generateCustomerPackage.isPending}
onClick={() =>
customerPackage
? setConfirmRegenerateOpen(true)
: generateCustomerPackage.mutate({
quotationId,