task-e
This commit is contained in:
@@ -40,6 +40,7 @@ import {
|
||||
deleteQuotationFollowupMutation,
|
||||
deleteQuotationItemMutation,
|
||||
deleteQuotationTopicMutation,
|
||||
updateQuotationMutation,
|
||||
updateQuotationAttachmentMutation,
|
||||
updateQuotationCustomerMutation,
|
||||
updateQuotationFollowupMutation,
|
||||
@@ -134,7 +135,15 @@ function ItemDialog({
|
||||
<Input value={values.description} onChange={(e) => setValues((s) => ({ ...s, description: e.target.value }))} placeholder='Description' />
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<Input value={values.quantity} onChange={(e) => setValues((s) => ({ ...s, quantity: e.target.value }))} type='number' placeholder='Quantity' />
|
||||
<Input value={values.unit} onChange={(e) => setValues((s) => ({ ...s, unit: e.target.value }))} placeholder='Unit' />
|
||||
<Select value={values.unit || '__none__'} onValueChange={(value) => setValues((s) => ({ ...s, unit: value === '__none__' ? '' : value }))}>
|
||||
<SelectTrigger><SelectValue placeholder='Unit' /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value='__none__'>No unit</SelectItem>
|
||||
{referenceData.units.map((option) => (
|
||||
<SelectItem key={option.id} value={option.id}>{option.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<Input value={values.unitPrice} onChange={(e) => setValues((s) => ({ ...s, unitPrice: e.target.value }))} type='number' placeholder='Unit price' />
|
||||
@@ -219,8 +228,8 @@ function CustomerDialog({
|
||||
<Select value={role} onValueChange={setRole}>
|
||||
<SelectTrigger><SelectValue placeholder='Role' /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{['owner', 'consultant', 'contractor', 'billing'].map((item) => (
|
||||
<SelectItem key={item} value={item}>{item}</SelectItem>
|
||||
{referenceData.customerRoles.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>{item.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -601,6 +610,12 @@ export function QuotationDetail({
|
||||
onSuccess: () => toast.success('Revision created successfully'),
|
||||
onError: (error) => toast.error(error instanceof Error ? error.message : 'Failed to create revision')
|
||||
});
|
||||
const submitForApproval = useMutation({
|
||||
...updateQuotationMutation,
|
||||
onSuccess: () => toast.success('Quotation moved to pending approval'),
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to submit for approval')
|
||||
});
|
||||
|
||||
const branchMap = useMemo(
|
||||
() => new Map(referenceData.branches.map((item) => [item.id, item.name])),
|
||||
@@ -637,6 +652,10 @@ export function QuotationDetail({
|
||||
const customer = customerMap.get(quotation.customerId);
|
||||
const contact = quotation.contactId ? contactMap.get(quotation.contactId) : null;
|
||||
const status = statusMap.get(quotation.status);
|
||||
const canReviseByStatus = ['accepted', 'rejected', 'sent', 'approved'].includes(
|
||||
status?.code ?? ''
|
||||
);
|
||||
const canSubmitApproval = ['draft', 'revised'].includes(status?.code ?? '');
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
@@ -795,7 +814,49 @@ export function QuotationDetail({
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{canCreateRevision ? (
|
||||
{canUpdate && canSubmitApproval ? (
|
||||
<Button
|
||||
variant='outline'
|
||||
isLoading={submitForApproval.isPending}
|
||||
onClick={() =>
|
||||
submitForApproval.mutate({
|
||||
id: quotationId,
|
||||
values: {
|
||||
enquiryId: quotation.enquiryId,
|
||||
customerId: quotation.customerId,
|
||||
contactId: quotation.contactId,
|
||||
quotationDate: quotation.quotationDate,
|
||||
validUntil: quotation.validUntil,
|
||||
quotationType: quotation.quotationType,
|
||||
projectName: quotation.projectName ?? '',
|
||||
projectLocation: quotation.projectLocation ?? '',
|
||||
attention: quotation.attention ?? '',
|
||||
branchId: quotation.branchId,
|
||||
currency: quotation.currency,
|
||||
exchangeRate: quotation.exchangeRate,
|
||||
status:
|
||||
referenceData.statuses.find((item) => item.code === 'pending_approval')?.id ??
|
||||
quotation.status,
|
||||
chancePercent: quotation.chancePercent,
|
||||
isHotProject: quotation.isHotProject,
|
||||
competitor: quotation.competitor ?? '',
|
||||
reference: quotation.reference ?? '',
|
||||
notes: quotation.notes ?? '',
|
||||
salesmanId: quotation.salesmanId,
|
||||
discount: quotation.discount,
|
||||
discountType: quotation.discountType,
|
||||
taxRate: quotation.taxRate,
|
||||
sentVia: quotation.sentVia,
|
||||
revisionRemark: quotation.revisionRemark,
|
||||
isActive: quotation.isActive
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Icons.send className='mr-2 h-4 w-4' /> Submit for approval
|
||||
</Button>
|
||||
) : null}
|
||||
{canCreateRevision && canReviseByStatus ? (
|
||||
<Button
|
||||
variant='outline'
|
||||
isLoading={createRevision.isPending}
|
||||
|
||||
Reference in New Issue
Block a user