thai-uxui
This commit is contained in:
@@ -36,11 +36,11 @@ export function QuotationCellAction({
|
||||
const deleteMutation = useMutation({
|
||||
...deleteQuotationMutation,
|
||||
onSuccess: () => {
|
||||
toast.success('Quotation deleted successfully');
|
||||
toast.success('ลบใบเสนอราคาสำเร็จ');
|
||||
setDeleteOpen(false);
|
||||
},
|
||||
onError: (error) =>
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to delete quotation')
|
||||
toast.error(error instanceof Error ? error.message : 'ไม่สามารถลบใบเสนอราคาได้')
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -65,15 +65,15 @@ export function QuotationCellAction({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='end'>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>การดำเนินการ</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => router.push(`/dashboard/crm/quotations/${data.id}`)}>
|
||||
<Icons.arrowRight className='mr-2 h-4 w-4' /> View
|
||||
<Icons.arrowRight className='mr-2 h-4 w-4' /> ดูรายละเอียด
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setEditOpen(true)} disabled={!canUpdate}>
|
||||
<Icons.edit className='mr-2 h-4 w-4' /> Edit
|
||||
<Icons.edit className='mr-2 h-4 w-4' /> แก้ไข
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setDeleteOpen(true)} disabled={!canDelete}>
|
||||
<Icons.trash className='mr-2 h-4 w-4' /> Delete
|
||||
<Icons.trash className='mr-2 h-4 w-4' /> ลบ
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function getQuotationColumns({
|
||||
id: 'code',
|
||||
accessorKey: 'code',
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Quotation' />
|
||||
<DataTableColumnHeader column={column} title='ใบเสนอราคา' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<div className='flex flex-col'>
|
||||
@@ -41,8 +41,8 @@ export function getQuotationColumns({
|
||||
</div>
|
||||
),
|
||||
meta: {
|
||||
label: 'Quotation',
|
||||
placeholder: 'Search quotations...',
|
||||
label: 'ใบเสนอราคา',
|
||||
placeholder: 'ค้นหาใบเสนอราคา...',
|
||||
variant: 'text' as const,
|
||||
icon: Icons.search
|
||||
},
|
||||
@@ -69,7 +69,7 @@ export function getQuotationColumns({
|
||||
id: 'quotationType',
|
||||
accessorKey: 'quotationType',
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Type' />
|
||||
<DataTableColumnHeader column={column} title='ประเภท' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Badge variant='outline'>
|
||||
@@ -77,7 +77,7 @@ export function getQuotationColumns({
|
||||
</Badge>
|
||||
),
|
||||
meta: {
|
||||
label: 'Type',
|
||||
label: 'ประเภท',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.quotationTypes.map((item) => ({ value: item.id, label: item.label }))
|
||||
},
|
||||
@@ -87,13 +87,13 @@ export function getQuotationColumns({
|
||||
id: 'branch',
|
||||
accessorFn: (row) => row.branchId ?? '',
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Branch' />
|
||||
<DataTableColumnHeader column={column} title='สาขา' />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<span>{row.original.branchId ? (branchMap.get(row.original.branchId)?.name ?? 'Unknown') : 'Unassigned'}</span>
|
||||
),
|
||||
meta: {
|
||||
label: 'Branch',
|
||||
label: 'สาขา',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.branches.map((item) => ({ value: item.id, label: item.name }))
|
||||
},
|
||||
@@ -103,11 +103,11 @@ export function getQuotationColumns({
|
||||
id: 'customer',
|
||||
accessorFn: (row) => row.customerId,
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Customer' />
|
||||
<DataTableColumnHeader column={column} title='ลูกค้าผู้รับใบเสนอราคา' />
|
||||
),
|
||||
cell: ({ row }) => row.original.customerName,
|
||||
meta: {
|
||||
label: 'Customer',
|
||||
label: 'ลูกค้าผู้รับใบเสนอราคา',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.customers.map((item) => ({ value: item.id, label: item.name }))
|
||||
},
|
||||
@@ -117,11 +117,11 @@ export function getQuotationColumns({
|
||||
id: 'enquiry',
|
||||
accessorFn: (row) => row.enquiryId ?? '',
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Enquiry' />
|
||||
<DataTableColumnHeader column={column} title='โอกาสขาย' />
|
||||
),
|
||||
cell: ({ row }) => row.original.enquiryCode ?? '-',
|
||||
meta: {
|
||||
label: 'Enquiry',
|
||||
label: 'โอกาสขาย',
|
||||
variant: 'multiSelect' as const,
|
||||
options: referenceData.enquiries.map((item) => ({ value: item.id, label: `${item.code} - ${item.title}` }))
|
||||
},
|
||||
@@ -131,15 +131,15 @@ export function getQuotationColumns({
|
||||
id: 'isHotProject',
|
||||
accessorFn: (row) => String(row.isHotProject),
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Hot' />
|
||||
<DataTableColumnHeader column={column} title='ด่วน' />
|
||||
),
|
||||
cell: ({ row }) => (row.original.isHotProject ? <Badge>Hot</Badge> : <span>-</span>),
|
||||
meta: {
|
||||
label: 'Hot Project',
|
||||
label: 'โครงการด่วน',
|
||||
variant: 'multiSelect' as const,
|
||||
options: [
|
||||
{ value: 'true', label: 'Hot' },
|
||||
{ value: 'false', label: 'Normal' }
|
||||
{ value: 'true', label: 'ด่วน' },
|
||||
{ value: 'false', label: 'ปกติ' }
|
||||
]
|
||||
},
|
||||
enableColumnFilter: true
|
||||
@@ -148,7 +148,7 @@ export function getQuotationColumns({
|
||||
id: 'totalAmount',
|
||||
accessorKey: 'totalAmount',
|
||||
header: ({ column }: { column: Column<QuotationListItem, unknown> }) => (
|
||||
<DataTableColumnHeader column={column} title='Total' />
|
||||
<DataTableColumnHeader column={column} title='มูลค่ารวม' />
|
||||
),
|
||||
cell: ({ row }) => row.original.totalAmount.toLocaleString()
|
||||
},
|
||||
|
||||
@@ -1324,7 +1324,7 @@ export function QuotationDetail({
|
||||
<TabsList className="flex flex-wrap">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="items">Items</TabsTrigger>
|
||||
<TabsTrigger value="customers">Project Parties</TabsTrigger>
|
||||
<TabsTrigger value="customers">ผู้เกี่ยวข้องในโครงการ</TabsTrigger>
|
||||
<TabsTrigger value="topics">Topics</TabsTrigger>
|
||||
<TabsTrigger value="followups">Follow-ups</TabsTrigger>
|
||||
<TabsTrigger value="attachments">Attachments</TabsTrigger>
|
||||
@@ -1343,7 +1343,7 @@ export function QuotationDetail({
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<FieldItem
|
||||
label="Billing Customer"
|
||||
label="ลูกค้าผู้รับใบเสนอราคา"
|
||||
value={customer?.name}
|
||||
/>
|
||||
<FieldItem label="Contact" value={contact?.name} />
|
||||
@@ -1406,7 +1406,7 @@ export function QuotationDetail({
|
||||
<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">
|
||||
@@ -1534,7 +1534,7 @@ export function QuotationDetail({
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Project Parties</CardTitle>
|
||||
<CardTitle>ผู้เกี่ยวข้องในโครงการ</CardTitle>
|
||||
<CardDescription>
|
||||
Related companies for this quotation, each with a
|
||||
visible role.
|
||||
|
||||
@@ -19,9 +19,9 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
const { data } = useSuspenseQuery(quotationDocumentPreviewOptions(quotationId));
|
||||
const { documentData, template } = data.preview;
|
||||
const topicGroups: Array<{ key: 'scope' | 'exclusion' | 'payment'; title: string }> = [
|
||||
{ key: 'scope', title: 'Scope' },
|
||||
{ key: 'exclusion', title: 'Exclusions' },
|
||||
{ key: 'payment', title: 'Payment Terms' }
|
||||
{ key: 'scope', title: 'ขอบเขตงาน' },
|
||||
{ key: 'exclusion', title: 'ข้อยกเว้น' },
|
||||
{ key: 'payment', title: 'เงื่อนไขการชำระเงิน' }
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -32,14 +32,14 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
<div className='space-y-1'>
|
||||
<CardTitle className='text-2xl'>{documentData.company.name}</CardTitle>
|
||||
<CardDescription>
|
||||
Quotation Preview using {template.template.templateName} v{template.version.version}
|
||||
ตัวอย่างใบเสนอราคา จากเทมเพลต {template.template.templateName} v{template.version.version}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{documentData.watermarkStatus ? (
|
||||
<Badge variant='destructive'>{documentData.watermarkStatus}</Badge>
|
||||
) : (
|
||||
<Badge>Approved Ready</Badge>
|
||||
<Badge>พร้อมใช้งานหลังอนุมัติ</Badge>
|
||||
)}
|
||||
<Badge variant='outline'>{template.template.fileType}</Badge>
|
||||
</div>
|
||||
@@ -49,41 +49,41 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
<div className='grid gap-6 lg:grid-cols-2'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Quotation Header</CardTitle>
|
||||
<CardDescription>Commercial identity and issue information.</CardDescription>
|
||||
<CardTitle>ข้อมูลหัวเอกสาร</CardTitle>
|
||||
<CardDescription>ข้อมูลอ้างอิงและวันออกเอกสารสำหรับใบเสนอราคา</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='grid gap-4 md:grid-cols-2'>
|
||||
<Field label='Quotation Code' value={documentData.quotation.code} />
|
||||
<Field label='เลขที่ใบเสนอราคา' value={documentData.quotation.code} />
|
||||
<Field label='Revision' value={documentData.quotation.revisionLabel} />
|
||||
<Field
|
||||
label='Quotation Date'
|
||||
label='วันที่ออกใบเสนอราคา'
|
||||
value={new Date(documentData.quotation.quotationDate).toLocaleDateString()}
|
||||
/>
|
||||
<Field
|
||||
label='Valid Until'
|
||||
label='ใช้ได้ถึง'
|
||||
value={
|
||||
documentData.quotation.validUntil
|
||||
? new Date(documentData.quotation.validUntil).toLocaleDateString()
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<Field label='Type' value={documentData.quotation.quotationTypeLabel} />
|
||||
<Field label='Branch' value={documentData.branch?.label} />
|
||||
<Field label='ประเภทเอกสาร' value={documentData.quotation.quotationTypeLabel} />
|
||||
<Field label='สาขา' value={documentData.branch?.label} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Customer Block</CardTitle>
|
||||
<CardDescription>Bill-to and attention context for the document.</CardDescription>
|
||||
<CardTitle>ข้อมูลลูกค้า</CardTitle>
|
||||
<CardDescription>ข้อมูลลูกค้าผู้รับใบเสนอราคาและผู้ติดต่อของเอกสาร</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='grid gap-4 md:grid-cols-2'>
|
||||
<Field label='Billing Customer' value={documentData.customer.name} />
|
||||
<Field label='Contact' value={documentData.contact?.name} />
|
||||
<Field label='Phone' value={documentData.customer.phone} />
|
||||
<Field label='Email' value={documentData.customer.email} />
|
||||
<Field label='ลูกค้าผู้รับใบเสนอราคา' value={documentData.customer.name} />
|
||||
<Field label='ผู้ติดต่อ' value={documentData.contact?.name} />
|
||||
<Field label='โทรศัพท์' value={documentData.customer.phone} />
|
||||
<Field label='อีเมล' value={documentData.customer.email} />
|
||||
<div className='md:col-span-2'>
|
||||
<Field label='Address' value={documentData.customer.address} />
|
||||
<Field label='ที่อยู่' value={documentData.customer.address} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -91,30 +91,30 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Project Information</CardTitle>
|
||||
<CardDescription>Scope summary and reference data for the customer.</CardDescription>
|
||||
<CardTitle>ข้อมูลโครงการ</CardTitle>
|
||||
<CardDescription>สรุปข้อมูลอ้างอิงของโครงการสำหรับลูกค้า</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
|
||||
<Field label='Project Name' value={documentData.quotation.projectName} />
|
||||
<Field label='Site Location' value={documentData.quotation.projectLocation} />
|
||||
<Field label='Attention' value={documentData.quotation.attention} />
|
||||
<Field label='Reference' value={documentData.quotation.reference} />
|
||||
<Field label='ชื่อโครงการ' value={documentData.quotation.projectName} />
|
||||
<Field label='สถานที่ติดตั้ง' value={documentData.quotation.projectLocation} />
|
||||
<Field label='เรียน' value={documentData.quotation.attention} />
|
||||
<Field label='อ้างอิง' value={documentData.quotation.reference} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Items Table</CardTitle>
|
||||
<CardDescription>Normalized line items prepared for template mapping.</CardDescription>
|
||||
<CardTitle>ตารางรายการ</CardTitle>
|
||||
<CardDescription>รายการที่จัดรูปแบบแล้วเพื่อเตรียมใช้กับ template mapping</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3'>
|
||||
<div className='grid grid-cols-[1.2fr_4fr_1fr_1.2fr_1.4fr_1.4fr] gap-3 rounded-lg border bg-muted/20 px-4 py-3 text-xs font-medium uppercase tracking-wide'>
|
||||
<div>Item</div>
|
||||
<div>Description</div>
|
||||
<div>Qty</div>
|
||||
<div>Unit</div>
|
||||
<div>Unit Price</div>
|
||||
<div>Total</div>
|
||||
<div>ลำดับ</div>
|
||||
<div>รายละเอียด</div>
|
||||
<div>จำนวน</div>
|
||||
<div>หน่วย</div>
|
||||
<div>ราคาต่อหน่วย</div>
|
||||
<div>รวม</div>
|
||||
</div>
|
||||
{documentData.items.map((item) => (
|
||||
<div
|
||||
@@ -138,8 +138,8 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
<div className='grid gap-6 lg:grid-cols-[2fr_1fr]'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Topics</CardTitle>
|
||||
<CardDescription>Scope, exclusions, and payment terms from topic records.</CardDescription>
|
||||
<CardTitle>หัวข้อเอกสาร</CardTitle>
|
||||
<CardDescription>ขอบเขตงาน ข้อยกเว้น และเงื่อนไขการชำระเงินจากหัวข้อในระบบ</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-6'>
|
||||
{topicGroups.map((group) => {
|
||||
@@ -148,7 +148,7 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
<div key={group.key} className='space-y-3'>
|
||||
<div className='font-medium'>{group.title}</div>
|
||||
{!entries.length ? (
|
||||
<div className='text-muted-foreground text-sm'>No entries</div>
|
||||
<div className='text-muted-foreground text-sm'>ไม่มีข้อมูล</div>
|
||||
) : (
|
||||
entries.map((entry) => (
|
||||
<div key={`${group.key}-${entry.title}`} className='rounded-lg border p-4'>
|
||||
@@ -170,19 +170,19 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
<div className='space-y-6'>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Price Summary</CardTitle>
|
||||
<CardTitle>สรุปมูลค่า</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3 text-sm'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>Subtotal</span>
|
||||
<span className='text-muted-foreground'>ก่อนภาษี</span>
|
||||
<span>{documentData.quotation.subtotal.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>Tax</span>
|
||||
<span className='text-muted-foreground'>ภาษี</span>
|
||||
<span>{documentData.quotation.taxAmount.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>Total</span>
|
||||
<span className='text-muted-foreground'>รวมทั้งสิ้น</span>
|
||||
<span className='font-semibold'>{documentData.quotation.totalAmount.toLocaleString()}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -190,24 +190,24 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Approval Block</CardTitle>
|
||||
<CardDescription>Prepared for watermark and approval snapshot flows.</CardDescription>
|
||||
<CardTitle>ข้อมูลอนุมัติเอกสาร</CardTitle>
|
||||
<CardDescription>ข้อมูลที่ใช้ประกอบ watermark และ approved snapshot</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
<Field label='Status' value={documentData.approval.status} />
|
||||
<Field label='สถานะ' value={documentData.approval.status} />
|
||||
<Field label='Workflow' value={documentData.approval.workflowName} />
|
||||
<Field label='Approved At' value={documentData.approval.approvedAt} />
|
||||
<Field label='อนุมัติเมื่อ' value={documentData.approval.approvedAt} />
|
||||
<Separator />
|
||||
{!documentData.approval.approvers.length ? (
|
||||
<div className='text-muted-foreground text-sm'>No approval actions recorded.</div>
|
||||
<div className='text-muted-foreground text-sm'>ยังไม่มีประวัติการอนุมัติ</div>
|
||||
) : (
|
||||
documentData.approval.approvers.map((item) => (
|
||||
<div key={`${item.stepNumber}-${item.roleCode}`} className='rounded-lg border p-3 text-sm'>
|
||||
<div className='font-medium'>
|
||||
Step {item.stepNumber} - {item.roleName}
|
||||
ขั้น {item.stepNumber} - {item.roleName}
|
||||
</div>
|
||||
<div className='text-muted-foreground'>
|
||||
{item.actorName || '-'} {item.actedAt ? `on ${new Date(item.actedAt).toLocaleString()}` : ''}
|
||||
{item.actorName || '-'} {item.actedAt ? `เมื่อ ${new Date(item.actedAt).toLocaleString()}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
@@ -217,17 +217,17 @@ export function QuotationDocumentPreview({ quotationId }: { quotationId: string
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Signature Placeholders</CardTitle>
|
||||
<CardTitle>ตำแหน่งลายเซ็น</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3 text-sm'>
|
||||
<Field label='Prepared By' value={documentData.signatures.preparedBy} />
|
||||
<Field label='Requested By' value={documentData.signatures.requestedBy} />
|
||||
<Field label='Sales Manager' value={documentData.signatures.salesManager} />
|
||||
<Field label='ผู้จัดทำ' value={documentData.signatures.preparedBy} />
|
||||
<Field label='ผู้ขออนุมัติ' value={documentData.signatures.requestedBy} />
|
||||
<Field label='ผู้จัดการฝ่ายขาย' value={documentData.signatures.salesManager} />
|
||||
<Field
|
||||
label='Department Manager'
|
||||
label='ผู้จัดการฝ่าย'
|
||||
value={documentData.signatures.departmentManager}
|
||||
/>
|
||||
<Field label='Top Manager' value={documentData.signatures.topManager} />
|
||||
<Field label='ผู้บริหารสูงสุด' value={documentData.signatures.topManager} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -268,7 +268,7 @@ export function QuotationFormSheet({
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
<Field label='Billing Customer'>
|
||||
<Field label='ลูกค้าผู้รับใบเสนอราคา'>
|
||||
<Select
|
||||
value={state.customerId}
|
||||
onValueChange={(value) => {
|
||||
|
||||
Reference in New Issue
Block a user