task-h
This commit is contained in:
@@ -52,6 +52,7 @@ import {
|
||||
quotationCustomersOptions,
|
||||
quotationFollowupsOptions,
|
||||
quotationItemsOptions,
|
||||
quotationKeys,
|
||||
quotationRevisionsOptions,
|
||||
quotationTopicsOptions
|
||||
} from '../api/queries';
|
||||
@@ -69,10 +70,12 @@ import type {
|
||||
QuotationTopicRecord
|
||||
} from '../api/types';
|
||||
import { submitQuotationApprovalMutation } from '@/features/foundation/approval/mutations';
|
||||
import { quotationDocumentKeys } from '@/features/crm/quotations/document/queries';
|
||||
import { QuotationDocumentPreview } from './quotation-document-preview';
|
||||
import { QuotationFormSheet } from './quotation-form-sheet';
|
||||
import { QuotationApprovalTab } from './quotation-approval-tab';
|
||||
import { QuotationStatusBadge } from './quotation-status-badge';
|
||||
import { getQueryClient } from '@/lib/query-client';
|
||||
|
||||
function FieldItem({ label, value }: { label: string; value: string | null | undefined }) {
|
||||
return (
|
||||
@@ -483,7 +486,10 @@ export function QuotationDetail({
|
||||
activeBusinessRole,
|
||||
isOrgAdmin,
|
||||
currentUserId,
|
||||
canPreviewDocument
|
||||
canPreviewDocument,
|
||||
canPreviewPdf,
|
||||
canDownloadPdf,
|
||||
canGenerateApprovedPdf
|
||||
}: {
|
||||
quotationId: string;
|
||||
referenceData: QuotationReferenceData;
|
||||
@@ -502,6 +508,9 @@ export function QuotationDetail({
|
||||
isOrgAdmin: boolean;
|
||||
currentUserId: string;
|
||||
canPreviewDocument: boolean;
|
||||
canPreviewPdf: boolean;
|
||||
canDownloadPdf: boolean;
|
||||
canGenerateApprovedPdf: boolean;
|
||||
}) {
|
||||
const { data } = useSuspenseQuery(quotationByIdOptions(quotationId));
|
||||
const { data: itemsData } = useSuspenseQuery(quotationItemsOptions(quotationId));
|
||||
@@ -634,6 +643,41 @@ export function QuotationDetail({
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to submit for approval')
|
||||
});
|
||||
const generateApprovedPdf = useMutation({
|
||||
mutationFn: async () => {
|
||||
const response = await fetch(`/api/crm/quotations/${quotationId}/approved-pdf`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = (await response.json().catch(() => null)) as { message?: string } | null;
|
||||
throw new Error(payload?.message ?? 'Failed to generate approved PDF');
|
||||
}
|
||||
|
||||
return response.blob();
|
||||
},
|
||||
onSuccess: async (blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = `${quotation.code}-approved.pdf`;
|
||||
document.body.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
const queryClient = getQueryClient();
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: quotationKeys.detail(quotationId) }),
|
||||
queryClient.invalidateQueries({ queryKey: quotationKeys.attachments(quotationId) }),
|
||||
queryClient.invalidateQueries({ queryKey: quotationDocumentKeys.data(quotationId) }),
|
||||
queryClient.invalidateQueries({ queryKey: quotationDocumentKeys.preview(quotationId) })
|
||||
]);
|
||||
toast.success('Approved PDF generated successfully');
|
||||
},
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to generate approved PDF')
|
||||
});
|
||||
|
||||
const branchMap = useMemo(
|
||||
() => new Map(referenceData.branches.map((item) => [item.id, item.name])),
|
||||
@@ -675,6 +719,8 @@ export function QuotationDetail({
|
||||
);
|
||||
const canSubmitCurrentQuotation =
|
||||
canSubmitApproval && ['draft', 'revised'].includes(status?.code ?? '');
|
||||
const canGenerateApprovedNow =
|
||||
canGenerateApprovedPdf && (status?.code ?? '') === 'approved';
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
@@ -856,6 +902,36 @@ export function QuotationDetail({
|
||||
<Icons.edit className='mr-2 h-4 w-4' /> Edit Quotation
|
||||
</Button>
|
||||
) : null}
|
||||
{canPreviewPdf ? (
|
||||
<Button asChild variant='outline'>
|
||||
<Link href={`/api/crm/quotations/${quotationId}/pdf-preview`} target='_blank'>
|
||||
<Icons.eyeOff className='mr-2 h-4 w-4' /> Preview PDF
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
{canDownloadPdf ? (
|
||||
<Button asChild variant='outline'>
|
||||
<Link href={`/api/crm/quotations/${quotationId}/pdf-download`} target='_blank'>
|
||||
<Icons.download className='mr-2 h-4 w-4' /> Download PDF
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
{canGenerateApprovedNow ? (
|
||||
<Button
|
||||
variant='outline'
|
||||
isLoading={generateApprovedPdf.isPending}
|
||||
onClick={() => generateApprovedPdf.mutate()}
|
||||
>
|
||||
<Icons.badgeCheck className='mr-2 h-4 w-4' /> Generate Approved PDF
|
||||
</Button>
|
||||
) : null}
|
||||
{quotation.approvedPdfUrl ? (
|
||||
<Button asChild variant='outline'>
|
||||
<Link href={`/api/crm/quotations/${quotationId}/approved-pdf`} target='_blank'>
|
||||
<Icons.externalLink className='mr-2 h-4 w-4' /> View Approved PDF
|
||||
</Link>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1246,6 +1322,8 @@ export function QuotationDetail({
|
||||
<FieldItem label='Created At' value={new Date(quotation.createdAt).toLocaleString()} />
|
||||
<FieldItem label='Updated At' value={new Date(quotation.updatedAt).toLocaleString()} />
|
||||
<FieldItem label='Revision' value={`R${String(quotation.revision).padStart(2, '0')}`} />
|
||||
<FieldItem label='Approved At' value={quotation.approvedAt ? new Date(quotation.approvedAt).toLocaleString() : null} />
|
||||
<FieldItem label='Approved Template Version' value={quotation.approvedTemplateVersionId} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user