'use client'; import * as React from 'react'; import { useEffect, useMemo, useState } from 'react'; import { useMutation, useQuery } from '@tanstack/react-query'; import { toast } from 'sonner'; import { Icons } from '@/components/icons'; import { Button } from '@/components/ui/button'; import { ProjectPartiesEditor, type ProjectPartyEditorItem } from '@/features/crm/components/project-parties-editor'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { Switch } from '@/components/ui/switch'; import { useAppForm, useFormFields } from '@/components/ui/tanstack-form'; import { opportunityProjectPartiesOptions } from '../api/queries'; import { createOpportunityMutation, updateOpportunityMutation } from '../api/mutations'; import type { OpportunityMutationPayload, OpportunityRecord, OpportunityReferenceData } from '../api/types'; import { opportunitySchema, type OpportunityFormValues } from '../schemas/opportunity.schema'; import { isHotProjectSuggested } from '@/features/crm/shared/hot-project'; function toDefaultValues( opportunity: OpportunityRecord | undefined, referenceData: OpportunityReferenceData ): OpportunityFormValues { return { customerId: opportunity?.customerId ?? referenceData.customers[0]?.id ?? '', contactId: opportunity?.contactId ?? undefined, assignedToUserId: opportunity?.assignedToUserId ?? undefined, title: opportunity?.title ?? '', description: opportunity?.description ?? '', requirement: opportunity?.requirement ?? '', projectName: opportunity?.projectName ?? '', projectLocation: opportunity?.projectLocation ?? '', branchId: opportunity?.branchId ?? undefined, productType: opportunity?.productType ?? referenceData.productTypes[0]?.id ?? '', status: opportunity?.status ?? referenceData.statuses[0]?.id ?? '', priority: opportunity?.priority ?? referenceData.priorities[1]?.id ?? referenceData.priorities[0]?.id ?? '', leadChannel: opportunity?.leadChannel ?? undefined, estimatedValue: opportunity?.estimatedValue ?? undefined, chancePercent: opportunity?.chancePercent ?? undefined, expectedCloseDate: opportunity?.expectedCloseDate ? opportunity.expectedCloseDate.slice(0, 10) : '', projectCloseDate: opportunity?.projectCloseDate ? opportunity.projectCloseDate.slice(0, 10) : '', deliveryDate: opportunity?.deliveryDate ? opportunity.deliveryDate.slice(0, 10) : '', competitor: opportunity?.competitor ?? '', source: opportunity?.source ?? '', isHotProject: opportunity?.isHotProject ?? false, hotProjectAutoSuggested: opportunity?.hotProjectAutoSuggested ?? false, hotProjectManuallyOverridden: opportunity?.hotProjectManuallyOverridden ?? false, notes: opportunity?.notes ?? '', isActive: opportunity?.isActive ?? true }; } export function OpportunityFormSheet({ opportunity, open, onOpenChange, referenceData, workspace = 'opportunity' }: { opportunity?: OpportunityRecord; open: boolean; onOpenChange: (open: boolean) => void; referenceData: OpportunityReferenceData; workspace?: 'lead' | 'opportunity'; }) { const isEdit = !!opportunity; const defaultValues = useMemo( () => toDefaultValues(opportunity, referenceData), [opportunity, referenceData] ); const [selectedCustomerId, setSelectedCustomerId] = useState(defaultValues.customerId); const [projectParties, setProjectParties] = useState([]); const { FormTextField, FormTextareaField, FormSelectField, FormSwitchField, FormDatePickerField, FormCurrencyField, FormPercentageField } = useFormFields(); const projectPartiesQuery = useQuery({ ...opportunityProjectPartiesOptions(opportunity?.id ?? ''), enabled: open && !!opportunity?.id }); const createMutation = useMutation({ ...createOpportunityMutation, onSuccess: () => { toast.success(`สร้าง${recordLabel}สำเร็จ`); onOpenChange(false); }, onError: (error) => toast.error(error instanceof Error ? error.message : `ไม่สามารถสร้าง${recordLabel}ได้`) }); const updateMutation = useMutation({ ...updateOpportunityMutation, onSuccess: () => { toast.success(`อัปเดต${recordLabel}สำเร็จ`); onOpenChange(false); }, onError: (error) => toast.error(error instanceof Error ? error.message : `ไม่สามารถอัปเดต${recordLabel}ได้`) }); const contactsForCustomer = referenceData.contacts.filter( (contact) => contact.customerId === selectedCustomerId ); const selectedCustomer = referenceData.customers.find((customer) => customer.id === selectedCustomerId); const form = useAppForm({ defaultValues, validators: { onSubmit: opportunitySchema }, onSubmit: async ({ value }) => { const payload: OpportunityMutationPayload = { customerId: value.customerId, contactId: value.contactId || null, assignedToUserId: value.assignedToUserId || null, title: value.title, description: value.description, requirement: value.requirement, projectName: value.projectName, projectLocation: value.projectLocation, branchId: value.branchId || null, productType: value.productType, status: value.status, priority: value.priority, leadChannel: value.leadChannel || null, estimatedValue: typeof value.estimatedValue === 'number' ? value.estimatedValue : null, chancePercent: typeof value.chancePercent === 'number' ? value.chancePercent : null, expectedCloseDate: value.expectedCloseDate || null, projectCloseDate: value.projectCloseDate || null, deliveryDate: value.deliveryDate || null, competitor: value.competitor, source: value.source, isHotProject: value.isHotProject ?? false, hotProjectAutoSuggested: value.hotProjectAutoSuggested ?? false, hotProjectManuallyOverridden: value.hotProjectManuallyOverridden ?? false, notes: value.notes, isActive: value.isActive ?? true, projectParties: projectParties .filter((item) => item.customerId && item.role) .map((item) => ({ customerId: item.customerId, role: item.role, remark: item.remark || null })) }; if (isEdit && opportunity) { await updateMutation.mutateAsync({ id: opportunity.id, values: payload }); return; } await createMutation.mutateAsync(payload); } }); useEffect(() => { if (!open) { return; } setSelectedCustomerId(defaultValues.customerId); form.reset(defaultValues); setProjectParties( opportunity ? (projectPartiesQuery.data?.items ?? []).map((item) => ({ key: item.id, customerId: item.customerId, role: item.role, remark: item.remark ?? '' })) : [] ); }, [defaultValues, opportunity, form, open, projectPartiesQuery.data?.items]); useEffect(() => { if (!open || isEdit) { return; } const ownerUserId = referenceData.customers.find((customer) => customer.id === selectedCustomerId)?.ownerUserId ?? ''; form.setFieldValue('assignedToUserId', ownerUserId); }, [form, isEdit, open, referenceData.customers, selectedCustomerId]); useEffect(() => { if (!open) { return; } const projectCloseDate = form.getFieldValue('projectCloseDate'); const manuallyOverridden = form.getFieldValue('hotProjectManuallyOverridden'); if (manuallyOverridden) { form.setFieldValue('hotProjectAutoSuggested', isHotProjectSuggested(projectCloseDate)); return; } const suggested = isHotProjectSuggested(projectCloseDate); form.setFieldValue('hotProjectAutoSuggested', suggested); form.setFieldValue('isHotProject', suggested); }, [form, open, form.state.values.projectCloseDate, form.state.values.hotProjectManuallyOverridden]); const isPending = createMutation.isPending || updateMutation.isPending; const recordLabel = workspace === 'lead' ? 'ลีด' : 'โอกาสขาย'; return ( {isEdit ? `แก้ไข${recordLabel}` : `เพิ่ม${recordLabel}`} {workspace === 'lead' ? 'บันทึกข้อมูลลีดของฝ่ายการตลาดก่อนส่งต่อให้ฝ่ายขาย' : 'บันทึกข้อมูลโอกาสขายของฝ่ายขายก่อนเริ่มทำใบเสนอราคา'}
( ลูกค้าผู้รับใบเสนอราคา * )} /> ( ผู้ติดต่อ )} /> ({ value: user.id, label: user.name }))} /> ({ value: branch.id, label: branch.name }))} /> ({ value: item.id, label: item.label }))} /> ({ value: item.id, label: item.label }))} /> ({ value: item.id, label: item.label }))} /> ({ value: item.id, label: item.label }))} />
(
โครงการด่วน ใช้สำหรับงานเร่งด่วนหรือโครงการสำคัญเชิงกลยุทธ์
{ field.handleChange(checked); field.handleBlur(); form.setFieldValue('hotProjectManuallyOverridden', true); form.setFieldValue( 'hotProjectAutoSuggested', isHotProjectSuggested(form.getFieldValue('projectCloseDate')) ); }} />
)} />
); } export function OpportunityFormSheetTrigger({ referenceData, workspace = 'opportunity' }: { referenceData: OpportunityReferenceData; workspace?: 'lead' | 'opportunity'; }) { const [open, setOpen] = React.useState(false); return ( <> ); }