next slice
This commit is contained in:
@@ -521,6 +521,45 @@ export const api = {
|
||||
|
||||
return response.blob();
|
||||
},
|
||||
async getQuotePdf(token: string, quoteId: string) {
|
||||
const response = await fetch(`/api/v1/documents/sales/quotes/${quoteId}/document.pdf`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError("Unable to render quote PDF.", "QUOTE_PDF_FAILED");
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
},
|
||||
async getSalesOrderPdf(token: string, orderId: string) {
|
||||
const response = await fetch(`/api/v1/documents/sales/orders/${orderId}/document.pdf`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError("Unable to render sales order PDF.", "SALES_ORDER_PDF_FAILED");
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
},
|
||||
async getPurchaseOrderPdf(token: string, orderId: string) {
|
||||
const response = await fetch(`/api/v1/documents/purchasing/orders/${orderId}/document.pdf`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError("Unable to render purchase order PDF.", "PURCHASE_ORDER_PDF_FAILED");
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
},
|
||||
async getCompanyProfilePreviewPdf(token: string) {
|
||||
const response = await fetch("/api/v1/documents/company-profile-preview.pdf", {
|
||||
headers: {
|
||||
|
||||
@@ -21,6 +21,7 @@ export function PurchaseDetailPage() {
|
||||
const [isSavingReceipt, setIsSavingReceipt] = useState(false);
|
||||
const [status, setStatus] = useState("Loading purchase order...");
|
||||
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
|
||||
const [isOpeningPdf, setIsOpeningPdf] = useState(false);
|
||||
|
||||
const canManage = user?.permissions.includes("purchasing.write") ?? false;
|
||||
const canReceive = canManage && (user?.permissions.includes(permissions.inventoryWrite) ?? false);
|
||||
@@ -158,6 +159,28 @@ export function PurchaseDetailPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOpenPdf() {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsOpeningPdf(true);
|
||||
setStatus("Rendering purchase order PDF...");
|
||||
|
||||
try {
|
||||
const blob = await api.getPurchaseOrderPdf(token, activeDocument.id);
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
window.open(objectUrl, "_blank", "noopener,noreferrer");
|
||||
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000);
|
||||
setStatus("Purchase order PDF ready.");
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : "Unable to render purchase order PDF.";
|
||||
setStatus(message);
|
||||
} finally {
|
||||
setIsOpeningPdf(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<div className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
@@ -174,6 +197,14 @@ export function PurchaseDetailPage() {
|
||||
<Link to="/purchasing/orders" className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Back to purchase orders
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenPdf}
|
||||
disabled={isOpeningPdf}
|
||||
className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{isOpeningPdf ? "Rendering PDF..." : "Open PDF"}
|
||||
</button>
|
||||
{canManage ? (
|
||||
<Link to={`/purchasing/orders/${activeDocument.id}/edit`} className="inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">
|
||||
Edit purchase order
|
||||
|
||||
@@ -20,6 +20,7 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
|
||||
const [status, setStatus] = useState(`Loading ${config.singularLabel.toLowerCase()}...`);
|
||||
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
|
||||
const [isConverting, setIsConverting] = useState(false);
|
||||
const [isOpeningPdf, setIsOpeningPdf] = useState(false);
|
||||
const [shipments, setShipments] = useState<ShipmentSummaryDto[]>([]);
|
||||
|
||||
const canManage = user?.permissions.includes(permissions.salesWrite) ?? false;
|
||||
@@ -93,6 +94,31 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOpenPdf() {
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsOpeningPdf(true);
|
||||
setStatus(`Rendering ${config.singularLabel.toLowerCase()} PDF...`);
|
||||
|
||||
try {
|
||||
const blob =
|
||||
entity === "quote"
|
||||
? await api.getQuotePdf(token, activeDocument.id)
|
||||
: await api.getSalesOrderPdf(token, activeDocument.id);
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
window.open(objectUrl, "_blank", "noopener,noreferrer");
|
||||
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 60_000);
|
||||
setStatus(`${config.singularLabel} PDF ready.`);
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof ApiError ? error.message : `Unable to render ${config.singularLabel.toLowerCase()} PDF.`;
|
||||
setStatus(message);
|
||||
} finally {
|
||||
setIsOpeningPdf(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<div className="rounded-[28px] border border-line/70 bg-surface/90 p-4 shadow-panel 2xl:p-5">
|
||||
@@ -109,6 +135,14 @@ export function SalesDetailPage({ entity }: { entity: SalesDocumentEntity }) {
|
||||
<Link to={config.routeBase} className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text">
|
||||
Back to {config.collectionLabel.toLowerCase()}
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenPdf}
|
||||
disabled={isOpeningPdf}
|
||||
className="inline-flex items-center justify-center rounded-2xl border border-line/70 px-2 py-2 text-sm font-semibold text-text disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{isOpeningPdf ? "Rendering PDF..." : "Open PDF"}
|
||||
</button>
|
||||
{canManage ? (
|
||||
<>
|
||||
<Link to={`${config.routeBase}/${activeDocument.id}/edit`} className="inline-flex items-center justify-center rounded-2xl bg-brand px-2 py-2 text-sm font-semibold text-white">
|
||||
|
||||
Reference in New Issue
Block a user