thai-uxui

This commit is contained in:
phaichayon
2026-06-19 11:11:17 +07:00
parent c0cc880be2
commit 721653f692
47 changed files with 1135 additions and 538 deletions

View File

@@ -51,21 +51,21 @@ export function ContactFormSheet({
const createMutation = useMutation({
...createCustomerContactMutation,
onSuccess: () => {
toast.success('Contact created successfully');
toast.success('สร้างผู้ติดต่อสำเร็จ');
onOpenChange(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Failed to create contact')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถสร้างผู้ติดต่อได้')
});
const updateMutation = useMutation({
...updateCustomerContactMutation,
onSuccess: () => {
toast.success('Contact updated successfully');
toast.success('อัปเดตผู้ติดต่อสำเร็จ');
onOpenChange(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Failed to update contact')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถอัปเดตผู้ติดต่อได้')
});
const form = useAppForm({
@@ -106,43 +106,41 @@ export function ContactFormSheet({
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className='flex flex-col sm:max-w-2xl'>
<SheetHeader>
<SheetTitle>{isEdit ? 'Edit Contact' : 'New Contact'}</SheetTitle>
<SheetDescription>
Capture the operational contact person for this customer account.
</SheetDescription>
<SheetTitle>{isEdit ? 'แก้ไขผู้ติดต่อ' : 'เพิ่มผู้ติดต่อ'}</SheetTitle>
<SheetDescription></SheetDescription>
</SheetHeader>
<div className='flex-1 overflow-auto'>
<form.AppForm>
<form.Form id='customer-contact-form' className='grid gap-4 md:grid-cols-2'>
<FormTextField name='name' label='Contact Name' required placeholder='Jane Doe' />
<FormTextField name='position' label='Position' placeholder='Procurement Manager' />
<FormTextField name='department' label='Department' placeholder='Procurement' />
<FormTextField name='phone' label='Phone' placeholder='02-000-0000' />
<FormTextField name='mobile' label='Mobile' placeholder='081-000-0000' />
<FormTextField name='name' label='ชื่อผู้ติดต่อ' required placeholder='Jane Doe' />
<FormTextField name='position' label='ตำแหน่ง' placeholder='Procurement Manager' />
<FormTextField name='department' label='แผนก' placeholder='Procurement' />
<FormTextField name='phone' label='โทรศัพท์' placeholder='02-000-0000' />
<FormTextField name='mobile' label='มือถือ' placeholder='081-000-0000' />
<FormTextField
name='email'
label='Email'
label='อีเมล'
type='email'
placeholder='jane@example.com'
/>
<div className='md:col-span-2'>
<FormTextareaField
name='notes'
label='Notes'
placeholder='Preferred communication style, escalation notes, or meeting context'
label='หมายเหตุ'
placeholder='รูปแบบการติดต่อที่เหมาะสม หมายเหตุการประสานงาน หรือบริบทการประชุม'
/>
</div>
<div className='md:col-span-2 grid gap-4 md:grid-cols-2'>
<FormSwitchField
name='isPrimary'
label='Primary Contact'
description='When enabled, this contact becomes the main person for this customer.'
label='ผู้ติดต่อหลัก'
description='เมื่อเปิดใช้งาน ผู้ติดต่อนี้จะเป็นผู้ติดต่อหลักของลูกค้ารายนี้'
/>
<FormSwitchField
name='isActive'
label='Active Contact'
description='Inactive contacts remain in history and can be restored later.'
label='ใช้งานอยู่'
description='ผู้ติดต่อที่ปิดใช้งานยังคงอยู่ในประวัติและเปิดกลับมาใช้ได้ภายหลัง'
/>
</div>
</form.Form>
@@ -151,11 +149,11 @@ export function ContactFormSheet({
<SheetFooter>
<Button type='button' variant='outline' onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type='submit' form='customer-contact-form' isLoading={isPending}>
<Icons.check className='mr-2 h-4 w-4' />
{isEdit ? 'Update Contact' : 'Create Contact'}
{isEdit ? 'อัปเดตผู้ติดต่อ' : 'สร้างผู้ติดต่อ'}
</Button>
</SheetFooter>
</SheetContent>

View File

@@ -36,11 +36,11 @@ export function CustomerCellAction({
const deleteMutation = useMutation({
...deleteCustomerMutation,
onSuccess: () => {
toast.success('Customer deleted successfully');
toast.success('ลบลูกค้าสำเร็จ');
setDeleteOpen(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Failed to delete customer')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถลบลูกค้าได้')
});
return (
@@ -65,15 +65,15 @@ export function CustomerCellAction({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuLabel></DropdownMenuLabel>
<DropdownMenuItem onClick={() => router.push(`/dashboard/crm/customers/${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>

View File

@@ -29,7 +29,7 @@ export function getCustomerColumns({
id: 'name',
accessorKey: 'name',
header: ({ column }: { column: Column<CustomerListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Customer' />
<DataTableColumnHeader column={column} title='ลูกค้า' />
),
cell: ({ row }) => (
<div className='flex flex-col'>
@@ -46,8 +46,8 @@ export function getCustomerColumns({
</div>
),
meta: {
label: 'Customer',
placeholder: 'Search customers...',
label: 'ลูกค้า',
placeholder: 'ค้นหาลูกค้า...',
variant: 'text' as const,
icon: Icons.search
},
@@ -85,7 +85,7 @@ export function getCustomerColumns({
</Badge>
),
meta: {
label: 'Type',
label: 'ประเภท',
variant: 'multiSelect' as const,
options: referenceData.customerTypes.map((item) => ({
value: item.id,
@@ -108,7 +108,7 @@ export function getCustomerColumns({
</span>
),
meta: {
label: 'Branch',
label: 'สาขา',
variant: 'multiSelect' as const,
options: referenceData.branches.map((item) => ({
value: item.id,
@@ -137,7 +137,7 @@ export function getCustomerColumns({
id: 'contactCount',
accessorKey: 'contactCount',
header: ({ column }: { column: Column<CustomerListItem, unknown> }) => (
<DataTableColumnHeader column={column} title='Contacts' />
<DataTableColumnHeader column={column} title='ผู้ติดต่อ' />
),
cell: ({ row }) => <span>{row.original.contactCount}</span>
},

View File

@@ -32,20 +32,20 @@ export function CustomerContactsTab({
const deleteMutation = useMutation({
...deleteCustomerContactMutation,
onSuccess: () => {
toast.success('Contact deleted successfully');
toast.success('ลบผู้ติดต่อสำเร็จ');
setDeleteOpen(false);
setSelectedId(null);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Failed to delete contact')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถลบผู้ติดต่อได้')
});
return (
<Card>
<CardHeader className='flex flex-row items-start justify-between gap-4'>
<div>
<CardTitle>Contacts</CardTitle>
<CardDescription>Manage the people attached to this customer account.</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</div>
{canCreate ? (
<Button
@@ -55,14 +55,14 @@ export function CustomerContactsTab({
setOpen(true);
}}
>
<Icons.add className='mr-2 h-4 w-4' /> Add Contact
<Icons.add className='mr-2 h-4 w-4' />
</Button>
) : null}
</CardHeader>
<CardContent className='space-y-4'>
{!data.items.length ? (
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm'>
No contacts have been added yet.
</div>
) : (
data.items.map((contact) => (
@@ -73,16 +73,16 @@ export function CustomerContactsTab({
<div className='space-y-1'>
<div className='flex flex-wrap items-center gap-2'>
<span className='font-medium'>{contact.name}</span>
{contact.isPrimary ? <Badge>Primary</Badge> : null}
{!contact.isActive ? <Badge variant='outline'>Inactive</Badge> : null}
{contact.isPrimary ? <Badge></Badge> : null}
{!contact.isActive ? <Badge variant='outline'></Badge> : null}
</div>
<div className='text-muted-foreground text-sm'>
{[contact.position, contact.department].filter(Boolean).join(' • ') ||
'No role details'}
'ยังไม่มีข้อมูลตำแหน่ง'}
</div>
<div className='text-muted-foreground text-sm'>
{[contact.phone, contact.mobile, contact.email].filter(Boolean).join(' • ') ||
'No contact methods'}
'ยังไม่มีช่องทางติดต่อ'}
</div>
{contact.notes ? <p className='text-sm'>{contact.notes}</p> : null}
</div>
@@ -96,7 +96,7 @@ export function CustomerContactsTab({
setOpen(true);
}}
>
<Icons.edit className='mr-2 h-4 w-4' /> Edit
<Icons.edit className='mr-2 h-4 w-4' />
</Button>
<Button
variant='outline'
@@ -107,7 +107,7 @@ export function CustomerContactsTab({
setDeleteOpen(true);
}}
>
<Icons.trash className='mr-2 h-4 w-4' /> Delete
<Icons.trash className='mr-2 h-4 w-4' />
</Button>
</div>
</div>

View File

@@ -100,14 +100,14 @@ export function CustomerDetail({
<div>
<h2 className='text-2xl font-semibold'>{customer.name}</h2>
<p className='text-muted-foreground text-sm'>
{customer.abbr || customer.email || customer.phone || 'Customer detail record'}
{customer.abbr || customer.email || customer.phone || 'รายละเอียดข้อมูลลูกค้า'}
</p>
</div>
</div>
<div className='flex flex-wrap gap-2'>
{canUpdate ? (
<Button variant='outline' onClick={() => setEditOpen(true)}>
<Icons.edit className='mr-2 h-4 w-4' /> Edit Customer
<Icons.edit className='mr-2 h-4 w-4' />
</Button>
) : null}
</div>
@@ -119,18 +119,16 @@ export function CustomerDetail({
<CardContent className='pt-6'>
<Tabs defaultValue='overview' className='gap-4'>
<TabsList>
<TabsTrigger value='overview'>Overview</TabsTrigger>
{canReadContacts ? <TabsTrigger value='contacts'>Contacts</TabsTrigger> : null}
<TabsTrigger value='activity'>Activity</TabsTrigger>
<TabsTrigger value='related'>Related Documents</TabsTrigger>
<TabsTrigger value='overview'></TabsTrigger>
{canReadContacts ? <TabsTrigger value='contacts'></TabsTrigger> : null}
<TabsTrigger value='activity'></TabsTrigger>
<TabsTrigger value='related'></TabsTrigger>
</TabsList>
<TabsContent value='overview'>
<Card>
<CardHeader>
<CardTitle>Overview</CardTitle>
<CardDescription>
Core account data, location, and CRM classification.
</CardDescription>
<CardTitle></CardTitle>
<CardDescription> CRM</CardDescription>
</CardHeader>
<CardContent className='grid gap-4 md:grid-cols-2 xl:grid-cols-4'>
<FieldItem label='Tax ID' value={customer.taxId} />
@@ -142,21 +140,21 @@ export function CustomerDetail({
value={customer.branchId ? branchMap.get(customer.branchId) : null}
/>
<FieldItem
label='Lead Channel'
label='ที่มาของลีด'
value={leadMap.get(customer.leadChannel ?? '')}
/>
<FieldItem
label='Customer Group'
label='กลุ่มลูกค้า'
value={groupMap.get(customer.customerGroup ?? '')}
/>
<FieldItem
label='Customer Sub Group'
label='กลุ่มย่อยลูกค้า'
value={subGroupMap.get(customer.customerSubGroup ?? '')}
/>
<FieldItem label='Active' value={customer.isActive ? 'Yes' : 'No'} />
<FieldItem label='ใช้งานอยู่' value={customer.isActive ? 'ใช่' : 'ไม่ใช่'} />
<div className='md:col-span-2 xl:col-span-4'>
<FieldItem
label='Address'
label='ที่อยู่'
value={
[
customer.address,
@@ -172,7 +170,7 @@ export function CustomerDetail({
/>
</div>
<div className='md:col-span-2 xl:col-span-4'>
<FieldItem label='Notes' value={customer.notes} />
<FieldItem label='หมายเหตุ' value={customer.notes} />
</div>
</CardContent>
</Card>
@@ -190,15 +188,13 @@ export function CustomerDetail({
<TabsContent value='activity'>
<Card>
<CardHeader>
<CardTitle>Activity</CardTitle>
<CardDescription>
Audit log entries captured from customer mutations.
</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
{!data.activity.length ? (
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm'>
No activity recorded yet.
</div>
) : (
data.activity.map((item, index) => (
@@ -229,15 +225,13 @@ export function CustomerDetail({
<TabsContent value='related'>
<Card>
<CardHeader>
<CardTitle>Related Documents</CardTitle>
<CardDescription>
Production enquiry and quotation links for this customer.
</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
{!relatedEnquiries.length && !relatedQuotations.length ? (
<div className='text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm'>
No enquiries or quotations have been linked to this customer yet.
</div>
) : (
<div className='space-y-3'>
@@ -288,8 +282,8 @@ export function CustomerDetail({
<div className='space-y-6'>
<Card>
<CardHeader>
<CardTitle>Record Snapshot</CardTitle>
<CardDescription>Operational metadata for this customer row.</CardDescription>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
<FieldItem label='Created By' value={customer.createdBy} />

View File

@@ -73,21 +73,21 @@ export function CustomerFormSheet({
const createMutation = useMutation({
...createCustomerMutation,
onSuccess: () => {
toast.success('Customer created successfully');
toast.success('สร้างลูกค้าสำเร็จ');
onOpenChange(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Failed to create customer')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถสร้างลูกค้าได้')
});
const updateMutation = useMutation({
...updateCustomerMutation,
onSuccess: () => {
toast.success('Customer updated successfully');
toast.success('อัปเดตลูกค้าสำเร็จ');
onOpenChange(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Failed to update customer')
toast.error(error instanceof Error ? error.message : 'ไม่สามารถอัปเดตลูกค้าได้')
});
const form = useAppForm({
@@ -146,10 +146,8 @@ export function CustomerFormSheet({
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className='flex flex-col sm:max-w-3xl'>
<SheetHeader>
<SheetTitle>{isEdit ? 'Edit Customer' : 'New Customer'}</SheetTitle>
<SheetDescription>
Maintain the core account profile, ownership branch, and CRM classification here.
</SheetDescription>
<SheetTitle>{isEdit ? 'แก้ไขลูกค้า' : 'เพิ่มลูกค้า'}</SheetTitle>
<SheetDescription> CRM</SheetDescription>
</SheetHeader>
<div className='flex-1 overflow-auto'>
@@ -157,25 +155,25 @@ export function CustomerFormSheet({
<form.Form id='customer-form-sheet' className='grid gap-4 md:grid-cols-2'>
<FormTextField
name='name'
label='Customer Name'
label='ชื่อลูกค้า'
required
placeholder='ALLA Service Co., Ltd.'
/>
<FormTextField name='abbr' label='Abbreviation' placeholder='ALLA' />
<FormTextField name='taxId' label='Tax ID' placeholder='0105556...' />
<FormTextField name='phone' label='Phone' placeholder='02-000-0000' />
<FormTextField name='fax' label='Fax' placeholder='02-000-0001' />
<FormTextField name='abbr' label='ชื่อย่อ' placeholder='ALLA' />
<FormTextField name='taxId' label='เลขประจำตัวผู้เสียภาษี' placeholder='0105556...' />
<FormTextField name='phone' label='โทรศัพท์' placeholder='02-000-0000' />
<FormTextField name='fax' label='แฟกซ์' placeholder='02-000-0001' />
<FormTextField
name='email'
label='Email'
label='อีเมล'
type='email'
placeholder='sales@example.com'
/>
<FormTextField name='website' label='Website' placeholder='https://example.com' />
<FormTextField name='country' label='Country' placeholder='Thailand' />
<FormTextField name='website' label='เว็บไซต์' placeholder='https://example.com' />
<FormTextField name='country' label='ประเทศ' placeholder='Thailand' />
<FormSelectField
name='customerType'
label='Customer Type'
label='ประเภทลูกค้า'
required
options={referenceData.customerTypes.map((item) => ({
value: item.id,
@@ -184,7 +182,7 @@ export function CustomerFormSheet({
/>
<FormSelectField
name='customerStatus'
label='Customer Status'
label='สถานะลูกค้า'
required
options={referenceData.customerStatuses.map((item) => ({
value: item.id,
@@ -193,7 +191,7 @@ export function CustomerFormSheet({
/>
<FormSelectField
name='branchId'
label='Branch'
label='สาขา'
options={referenceData.branches.map((branch) => ({
value: branch.id,
label: branch.name
@@ -201,7 +199,7 @@ export function CustomerFormSheet({
/>
<FormSelectField
name='leadChannel'
label='Lead Channel'
label='ที่มาของลีด'
options={referenceData.leadChannels.map((item) => ({
value: item.id,
label: item.label
@@ -209,7 +207,7 @@ export function CustomerFormSheet({
/>
<FormSelectField
name='customerGroup'
label='Customer Group'
label='กลุ่มลูกค้า'
options={referenceData.customerGroups.map((item) => ({
value: item.id,
label: item.label
@@ -217,10 +215,10 @@ export function CustomerFormSheet({
/>
<FormSelectField
name='customerSubGroup'
label='Customer Sub Group'
label='กลุ่มย่อยลูกค้า'
disabled={!selectedCustomerGroup}
placeholder={
selectedCustomerGroup ? 'Select customer sub group' : 'Select customer group first'
selectedCustomerGroup ? 'เลือกกลุ่มย่อยลูกค้า' : 'เลือกกลุ่มลูกค้าก่อน'
}
options={filteredCustomerSubGroups.map((item) => ({
value: item.id,
@@ -228,24 +226,24 @@ export function CustomerFormSheet({
}))}
/>
<div className='md:col-span-2'>
<FormTextField name='address' label='Address' placeholder='123 Main Road' />
<FormTextField name='address' label='ที่อยู่' placeholder='123 Main Road' />
</div>
<FormTextField name='province' label='Province' placeholder='Bangkok' />
<FormTextField name='district' label='District' placeholder='Bang Kapi' />
<FormTextField name='subDistrict' label='Sub District' placeholder='Hua Mak' />
<FormTextField name='postalCode' label='Postal Code' placeholder='10240' />
<FormTextField name='province' label='จังหวัด' placeholder='Bangkok' />
<FormTextField name='district' label='เขต / อำเภอ' placeholder='Bang Kapi' />
<FormTextField name='subDistrict' label='แขวง / ตำบล' placeholder='Hua Mak' />
<FormTextField name='postalCode' label='รหัสไปรษณีย์' placeholder='10240' />
<div className='md:col-span-2'>
<FormTextareaField
name='notes'
label='Notes'
placeholder='Internal notes about this customer relationship'
label='หมายเหตุ'
placeholder='หมายเหตุภายในเกี่ยวกับความสัมพันธ์กับลูกค้ารายนี้'
/>
</div>
<div className='md:col-span-2'>
<FormSwitchField
name='isActive'
label='Active Record'
description='Inactive customers remain visible in history but should not be used for new work.'
label='ใช้งานอยู่'
description='ลูกค้าที่ปิดใช้งานยังคงอยู่ในประวัติ แต่ไม่ควรนำไปใช้กับงานใหม่'
/>
</div>
</form.Form>
@@ -254,11 +252,11 @@ export function CustomerFormSheet({
<SheetFooter>
<Button type='button' variant='outline' onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button type='submit' form='customer-form-sheet' isLoading={isPending}>
<Icons.check className='mr-2 h-4 w-4' />
{isEdit ? 'Update Customer' : 'Create Customer'}
{isEdit ? 'อัปเดตลูกค้า' : 'สร้างลูกค้า'}
</Button>
</SheetFooter>
</SheetContent>
@@ -276,7 +274,7 @@ export function CustomerFormSheetTrigger({
return (
<>
<Button onClick={() => setOpen(true)}>
<Icons.add className='mr-2 h-4 w-4' /> Add Customer
<Icons.add className='mr-2 h-4 w-4' />
</Button>
<CustomerFormSheet open={open} onOpenChange={setOpen} referenceData={referenceData} />
</>