Files
alla-allaos-fullstack/src/features/crm/customers/components/customer-form-sheet.tsx
phaichayon c1ecd5ea50 commit
2026-06-23 22:13:08 +07:00

284 lines
11 KiB
TypeScript

'use client';
import * as React from 'react';
import { useEffect, useMemo } from 'react';
import { useStore } from '@tanstack/react-form';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Icons } from '@/components/icons';
import { Button } from '@/components/ui/button';
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle
} from '@/components/ui/sheet';
import { useAppForm, useFormFields } from '@/components/ui/tanstack-form';
import { createCustomerMutation, updateCustomerMutation } from '../api/mutations';
import type { CustomerMutationPayload, CustomerRecord, CustomerReferenceData } from '../api/types';
import { customerSchema, type CustomerFormValues } from '../schemas/customer.schema';
interface CustomerFormSheetProps {
customer?: CustomerRecord;
open: boolean;
onOpenChange: (open: boolean) => void;
referenceData: CustomerReferenceData;
}
function toDefaultValues(
customer: CustomerRecord | undefined,
referenceData: CustomerReferenceData
): CustomerFormValues {
return {
name: customer?.name ?? '',
abbr: customer?.abbr ?? '',
taxId: customer?.taxId ?? '',
customerType: customer?.customerType ?? referenceData.customerTypes[0]?.id ?? '',
customerStatus: customer?.customerStatus ?? referenceData.customerStatuses[0]?.id ?? '',
branchId: customer?.branchId ?? undefined,
address: customer?.address ?? '',
province: customer?.province ?? '',
district: customer?.district ?? '',
subDistrict: customer?.subDistrict ?? '',
postalCode: customer?.postalCode ?? '',
country: customer?.country ?? 'Thailand',
phone: customer?.phone ?? '',
fax: customer?.fax ?? '',
email: customer?.email ?? '',
website: customer?.website ?? '',
leadChannel: customer?.leadChannel ?? undefined,
customerGroup: customer?.customerGroup ?? undefined,
customerSubGroup: customer?.customerSubGroup ?? undefined,
notes: customer?.notes ?? '',
isActive: customer?.isActive ?? true
};
}
export function CustomerFormSheet({
customer,
open,
onOpenChange,
referenceData
}: CustomerFormSheetProps) {
const isEdit = !!customer;
const defaultValues = useMemo(
() => toDefaultValues(customer, referenceData),
[customer, referenceData]
);
const { FormTextField, FormSelectField, FormTextareaField, FormSwitchField } =
useFormFields<CustomerFormValues>();
const createMutation = useMutation({
...createCustomerMutation,
onSuccess: () => {
toast.success('สร้างลูกค้าสำเร็จ');
onOpenChange(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'ไม่สามารถสร้างลูกค้าได้')
});
const updateMutation = useMutation({
...updateCustomerMutation,
onSuccess: () => {
toast.success('อัปเดตลูกค้าสำเร็จ');
onOpenChange(false);
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'ไม่สามารถอัปเดตลูกค้าได้')
});
const form = useAppForm({
defaultValues,
validators: {
onSubmit: customerSchema
},
onSubmit: async ({ value }) => {
const payload: CustomerMutationPayload = {
...value,
branchId: value.branchId || null,
leadChannel: value.leadChannel || null,
customerGroup: value.customerGroup || null,
customerSubGroup: value.customerSubGroup || null
};
if (isEdit && customer) {
await updateMutation.mutateAsync({ id: customer.id, values: payload });
return;
}
await createMutation.mutateAsync(payload);
}
});
useEffect(() => {
if (!open) {
return;
}
form.reset(defaultValues);
}, [defaultValues, form, open]);
const selectedCustomerGroup = useStore(form.store, (state) => state.values.customerGroup);
const selectedCustomerSubGroup = useStore(form.store, (state) => state.values.customerSubGroup);
const filteredCustomerSubGroups = useMemo(
() =>
referenceData.customerSubGroups.filter((item) => item.parentId === selectedCustomerGroup),
[referenceData.customerSubGroups, selectedCustomerGroup]
);
useEffect(() => {
if (!selectedCustomerSubGroup) {
return;
}
const isValid = filteredCustomerSubGroups.some((item) => item.id === selectedCustomerSubGroup);
if (!isValid) {
form.setFieldValue('customerSubGroup', '');
}
}, [filteredCustomerSubGroups, form, selectedCustomerSubGroup]);
const isPending = createMutation.isPending || updateMutation.isPending;
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className='flex flex-col sm:max-w-3xl'>
<SheetHeader>
<SheetTitle>{isEdit ? 'แก้ไขลูกค้า' : 'เพิ่มลูกค้า'}</SheetTitle>
<SheetDescription> CRM</SheetDescription>
</SheetHeader>
<div className='flex-1 overflow-auto'>
<form.AppForm>
<form.Form id='customer-form-sheet' className='grid gap-4 md:grid-cols-2'>
<FormTextField
name='name'
label='ชื่อลูกค้า'
required
placeholder='ALLA Service Co., Ltd.'
/>
<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='อีเมล'
type='email'
placeholder='sales@example.com'
/>
<FormTextField name='website' label='เว็บไซต์' placeholder='https://example.com' />
<FormTextField name='country' label='ประเทศ' placeholder='Thailand' />
<FormSelectField
name='customerType'
label='ประเภทลูกค้า'
required
options={referenceData.customerTypes.map((item) => ({
value: item.id,
label: item.label
}))}
/>
<FormSelectField
name='customerStatus'
label='สถานะลูกค้า'
required
options={referenceData.customerStatuses.map((item) => ({
value: item.id,
label: item.label
}))}
/>
<FormSelectField
name='branchId'
label='สาขา'
options={referenceData.branches.map((branch) => ({
value: branch.id,
label: branch.name
}))}
/>
<FormSelectField
name='leadChannel'
label='ที่มาของลีด'
options={referenceData.leadChannels.map((item) => ({
value: item.id,
label: item.label
}))}
/>
<FormSelectField
name='customerGroup'
label='กลุ่มลูกค้า'
options={referenceData.customerGroups.map((item) => ({
value: item.id,
label: item.label
}))}
/>
<FormSelectField
name='customerSubGroup'
label='กลุ่มย่อยลูกค้า'
disabled={!selectedCustomerGroup}
placeholder={
selectedCustomerGroup ? 'เลือกกลุ่มย่อยลูกค้า' : 'เลือกกลุ่มลูกค้าก่อน'
}
options={filteredCustomerSubGroups.map((item) => ({
value: item.id,
label: item.label
}))}
/>
<div className='md:col-span-2'>
<FormTextField name='address' label='ที่อยู่' placeholder='123 Main Road' />
</div>
<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'
size='md'
label='หมายเหตุ'
placeholder='หมายเหตุภายในเกี่ยวกับความสัมพันธ์กับลูกค้ารายนี้'
/>
</div>
<div className='md:col-span-2'>
<FormSwitchField
name='isActive'
label='ใช้งานอยู่'
description='ลูกค้าที่ปิดใช้งานยังคงอยู่ในประวัติ แต่ไม่ควรนำไปใช้กับงานใหม่'
/>
</div>
</form.Form>
</form.AppForm>
</div>
<SheetFooter>
<Button type='button' variant='outline' onClick={() => onOpenChange(false)}>
</Button>
<Button type='submit' form='customer-form-sheet' isLoading={isPending}>
<Icons.check className='mr-2 h-4 w-4' />
{isEdit ? 'อัปเดตลูกค้า' : 'สร้างลูกค้า'}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
export function CustomerFormSheetTrigger({
referenceData
}: {
referenceData: CustomerReferenceData;
}) {
const [open, setOpen] = React.useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>
<Icons.add className='mr-2 h-4 w-4' />
</Button>
<CustomerFormSheet open={open} onOpenChange={setOpen} referenceData={referenceData} />
</>
);
}