taks-d.2.1
This commit is contained in:
@@ -268,16 +268,18 @@ function CustomerDialog({
|
||||
const [customerId, setCustomerId] = useState(
|
||||
relation?.customerId ?? referenceData.customers[0]?.id ?? ''
|
||||
);
|
||||
const [role, setRole] = useState(relation?.role ?? 'owner');
|
||||
const [isPrimary, setIsPrimary] = useState(relation?.isPrimary ?? false);
|
||||
const [role, setRole] = useState(
|
||||
relation?.role ?? referenceData.projectPartyRoles[0]?.id ?? ''
|
||||
);
|
||||
const [remark, setRemark] = useState(relation?.remark ?? '');
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{relation ? 'Edit Related Customer' : 'Add Related Customer'}</DialogTitle>
|
||||
<DialogTitle>{relation ? 'Edit Project Party' : 'Add Project Party'}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Keep owner, consultant, contractor, and billing parties visible on the quote.
|
||||
Keep related companies visible with their role in this quotation.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className='grid gap-4'>
|
||||
@@ -298,22 +300,14 @@ function CustomerDialog({
|
||||
<SelectValue placeholder='Role' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{referenceData.customerRoles.map((item) => (
|
||||
{referenceData.projectPartyRoles.map((item) => (
|
||||
<SelectItem key={item.id} value={item.id}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<label className='flex items-center justify-between rounded-lg border px-4 py-3 text-sm'>
|
||||
<span>Primary relation</span>
|
||||
<input
|
||||
aria-label='Primary relation'
|
||||
type='checkbox'
|
||||
checked={isPrimary}
|
||||
onChange={(e) => setIsPrimary(e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
<Input value={remark} onChange={(event) => setRemark(event.target.value)} placeholder='Remark' />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant='outline' onClick={() => onOpenChange(false)}>
|
||||
@@ -322,7 +316,7 @@ function CustomerDialog({
|
||||
<Button
|
||||
isLoading={pending}
|
||||
onClick={async () => {
|
||||
await onSubmit({ customerId, role, isPrimary });
|
||||
await onSubmit({ customerId, role, remark: remark || null });
|
||||
onOpenChange(false);
|
||||
}}
|
||||
>
|
||||
@@ -720,24 +714,24 @@ export function QuotationDetail({
|
||||
const [deleteCustomerId, setDeleteCustomerId] = useState<string | null>(null);
|
||||
const createCustomer = useMutation({
|
||||
...createQuotationCustomerMutation,
|
||||
onSuccess: () => toast.success('Related customer saved'),
|
||||
onSuccess: () => toast.success('Project party saved'),
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to save customer')
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to save project party')
|
||||
});
|
||||
const updateCustomer = useMutation({
|
||||
...updateQuotationCustomerMutation,
|
||||
onSuccess: () => toast.success('Related customer updated'),
|
||||
onSuccess: () => toast.success('Project party updated'),
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to update customer')
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to update project party')
|
||||
});
|
||||
const deleteCustomer = useMutation({
|
||||
...deleteQuotationCustomerMutation,
|
||||
onSuccess: () => {
|
||||
toast.success('Related customer deleted');
|
||||
toast.success('Project party deleted');
|
||||
setDeleteCustomerId(null);
|
||||
},
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to delete customer')
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to delete project party')
|
||||
});
|
||||
|
||||
const [topicOpen, setTopicOpen] = useState(false);
|
||||
@@ -1137,7 +1131,7 @@ export function QuotationDetail({
|
||||
<TabsList className='flex flex-wrap'>
|
||||
<TabsTrigger value='overview'>Overview</TabsTrigger>
|
||||
<TabsTrigger value='items'>Items</TabsTrigger>
|
||||
<TabsTrigger value='customers'>Customers</TabsTrigger>
|
||||
<TabsTrigger value='customers'>Project Parties</TabsTrigger>
|
||||
<TabsTrigger value='topics'>Topics</TabsTrigger>
|
||||
<TabsTrigger value='followups'>Follow-ups</TabsTrigger>
|
||||
<TabsTrigger value='attachments'>Attachments</TabsTrigger>
|
||||
@@ -1155,7 +1149,7 @@ export function QuotationDetail({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
|
||||
<FieldItem label='Customer' value={customer?.name} />
|
||||
<FieldItem label='Billing Customer' value={customer?.name} />
|
||||
<FieldItem label='Contact' value={contact?.name} />
|
||||
<FieldItem
|
||||
label='Branch'
|
||||
@@ -1188,6 +1182,28 @@ export function QuotationDetail({
|
||||
quotation.chancePercent !== null ? `${quotation.chancePercent}%` : null
|
||||
}
|
||||
/>
|
||||
<div className='md:col-span-2 xl:col-span-4'>
|
||||
<div className='space-y-3'>
|
||||
<div className='text-muted-foreground text-xs'>Project Parties</div>
|
||||
{!customersData.items.length ? (
|
||||
<div className='text-muted-foreground rounded-lg border border-dashed p-4 text-sm'>
|
||||
No project parties have been added yet.
|
||||
</div>
|
||||
) : (
|
||||
<div className='space-y-3'>
|
||||
{customersData.items.map((item) => (
|
||||
<div key={item.id} className='rounded-lg border p-4 text-sm'>
|
||||
<div className='font-medium'>{item.customerName}</div>
|
||||
<div className='text-muted-foreground'>
|
||||
Role: {item.roleLabel ?? item.roleCode}
|
||||
</div>
|
||||
{item.remark ? <div className='mt-1'>{item.remark}</div> : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='md:col-span-2 xl:col-span-4'>
|
||||
<FieldItem
|
||||
label='Project'
|
||||
@@ -1285,8 +1301,10 @@ export function QuotationDetail({
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between'>
|
||||
<div>
|
||||
<CardTitle>Customers</CardTitle>
|
||||
<CardDescription>All parties involved in this quotation.</CardDescription>
|
||||
<CardTitle>Project Parties</CardTitle>
|
||||
<CardDescription>
|
||||
Related companies for this quotation, each with a visible role.
|
||||
</CardDescription>
|
||||
</div>
|
||||
{canManageCustomers ? (
|
||||
<Button
|
||||
@@ -1295,13 +1313,13 @@ export function QuotationDetail({
|
||||
setCustomerOpen(true);
|
||||
}}
|
||||
>
|
||||
<Icons.add className='mr-2 h-4 w-4' /> Add Customer
|
||||
<Icons.add className='mr-2 h-4 w-4' /> Add Party
|
||||
</Button>
|
||||
) : null}
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3'>
|
||||
{!customersData.items.length ? (
|
||||
<EmptyState message='No additional customer relations yet.' />
|
||||
<EmptyState message='No project parties have been added yet.' />
|
||||
) : (
|
||||
customersData.items.map((item) => (
|
||||
<div
|
||||
@@ -1311,9 +1329,11 @@ export function QuotationDetail({
|
||||
<div>
|
||||
<div className='font-medium'>{item.customerName}</div>
|
||||
<div className='text-muted-foreground text-sm'>
|
||||
{item.role}
|
||||
{item.isPrimary ? ' - Primary' : ''}
|
||||
Role: {item.roleLabel ?? item.roleCode}
|
||||
</div>
|
||||
{item.remark ? (
|
||||
<div className='text-muted-foreground mt-1 text-sm'>{item.remark}</div>
|
||||
) : null}
|
||||
</div>
|
||||
{canManageCustomers ? (
|
||||
<div className='flex gap-2'>
|
||||
|
||||
Reference in New Issue
Block a user