'use client'; import { useForm, type SubmitErrorHandler } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Card } from '@/components/ui/card'; import { Loader2 } from 'lucide-react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useRouter } from 'next/navigation'; import { useCreateRFA } from '@/hooks/use-rfa'; import { useDrawings } from '@/hooks/use-drawing'; import { useDisciplines, useContracts, useOrganizations } from '@/hooks/use-master-data'; import { useCorrespondenceTypes, useRfaTypes } from '@/hooks/use-reference-data'; import { useProjects } from '@/hooks/use-projects'; import { CreateRfaDto } from '@/types/dto/rfa/rfa.dto'; import { useState, useEffect, type FormEvent } from 'react'; import { correspondenceService } from '@/lib/services/correspondence.service'; import { Contract } from '@/types/contract'; const rfaSchema = z.object({ projectId: z.string().min(1, 'Project is required'), // ADR-019: UUID contractId: z.string().min(1, 'Contract is required'), disciplineId: z.union([z.string().min(1, 'Discipline is required'), z.number().min(1, 'Discipline is required')]), rfaTypeId: z.string().min(1, 'Type is required'), // ADR-019: UUID subject: z.string().min(5, 'Subject must be at least 5 characters'), description: z.string().optional(), body: z.string().optional(), remarks: z.string().optional(), toOrganizationId: z.string().min(1, 'Please select To Organization'), dueDate: z.string().optional(), shopDrawingRevisionIds: z.array(z.string()).optional(), asBuiltDrawingRevisionIds: z.array(z.string()).optional(), }); type RFAFormData = z.infer; type ProjectOption = { publicId?: string; projectName?: string; projectCode?: string; }; type ContractOption = { publicId?: string; contractName?: string; name?: string; contractCode?: string; }; type DisciplineOption = { publicId?: string; // ADR-019: public identifier id?: number; // Internal INT disciplineCode: string; codeNameEn?: string; codeNameTh?: string; }; type RfaTypeOption = { publicId?: string; // ADR-019: public identifier id?: number; // Internal INT typeCode?: string; typeName?: string; typeNameEn?: string; typeNameTh?: string; }; type CorrespondenceTypeOption = { publicId: string; // ADR-019: public identifier typeCode?: string; typeName?: string; }; type OrganizationOption = { publicId?: string; organizationCode?: string; organizationName?: string; }; type SelectableDrawingOption = { publicId?: string; drawingNumber?: string; title?: string; legacyDrawingNumber?: string; currentRevisionPublicId?: string; currentRevision?: { publicId?: string; revisionLabel?: string; revisionNumber?: number | string; title?: string; legacyDrawingNumber?: string; }; }; const extractArrayData = (value: unknown): T[] => { let current: unknown = value; for (let i = 0; i < 5; i += 1) { if (Array.isArray(current)) { return current as T[]; } if (!current || typeof current !== 'object' || !('data' in current)) { return []; } current = (current as { data?: unknown }).data; } return Array.isArray(current) ? (current as T[]) : []; }; const dedupeByKey = (items: T[], getKey: (item: T) => string | number | undefined): T[] => { const seen = new Set(); return items.filter((item) => { const key = getKey(item); if (key === undefined || key === '' || seen.has(key)) { return false; } seen.add(key); return true; }); }; const getOptionValue = (value?: string | number): string | undefined => { if (value === undefined || value === null || value === '') { return undefined; } return String(value); }; const getMasterOptionValue = (option: { publicId?: string; id?: number }): string | undefined => { return getOptionValue(option.publicId ?? option.id); }; export function RFAForm() { const router = useRouter(); const createMutation = useCreateRFA(); const { data: projectsData, isLoading: isLoadingProjects } = useProjects(); const projects = dedupeByKey(extractArrayData(projectsData), (project) => project.publicId); const { data: organizationsData, isLoading: isLoadingOrganizations } = useOrganizations({ isActive: true }); const organizations = dedupeByKey( extractArrayData(organizationsData), (organization) => organization.publicId ); const { data: correspondenceTypesData } = useCorrespondenceTypes(); const correspondenceTypes = extractArrayData(correspondenceTypesData); const rfaCorrespondenceType = correspondenceTypes.find((type) => type.typeCode?.toUpperCase() === 'RFA'); const { register, handleSubmit, setValue, setError, clearErrors, watch, formState: { errors }, } = useForm({ resolver: zodResolver(rfaSchema), defaultValues: { projectId: '', contractId: '', disciplineId: '', rfaTypeId: '', subject: '', description: '', body: '', remarks: '', toOrganizationId: '', dueDate: '', shopDrawingRevisionIds: [], asBuiltDrawingRevisionIds: [], }, }); const selectedProjectId = watch('projectId'); const { data: contractsData, isLoading: isLoadingContracts } = useContracts(selectedProjectId); const contracts = dedupeByKey( extractArrayData(contractsData), (contract) => contract.publicId ); const selectedContractId = watch('contractId'); const { data: disciplinesData, isLoading: isLoadingDisciplines } = useDisciplines(selectedContractId); const disciplines = dedupeByKey( extractArrayData(disciplinesData), (discipline) => getMasterOptionValue(discipline) ); const { data: rfaTypesData, isLoading: isLoadingRfaTypes } = useRfaTypes(selectedContractId); const rfaTypes = dedupeByKey(extractArrayData(rfaTypesData), (rfaType) => getMasterOptionValue(rfaType)); const [shopDrawingSearch, setShopDrawingSearch] = useState(''); const [shopDrawingPage, setShopDrawingPage] = useState(1); const { data: shopDrawingsData, isLoading: isLoadingShopDrawings } = useDrawings('SHOP', { projectUuid: selectedProjectId || '', search: shopDrawingSearch, page: shopDrawingPage, limit: 10, }); const shopDrawings = dedupeByKey( extractArrayData(shopDrawingsData), (drawing) => drawing.currentRevisionPublicId ?? drawing.currentRevision?.publicId ?? drawing.publicId ); const [asBuiltDrawingSearch, setAsBuiltDrawingSearch] = useState(''); const [asBuiltDrawingPage, setAsBuiltDrawingPage] = useState(1); const { data: asBuiltDrawingsData, isLoading: isLoadingAsBuiltDrawings } = useDrawings('AS_BUILT', { projectUuid: selectedProjectId || '', search: asBuiltDrawingSearch, page: asBuiltDrawingPage, limit: 10, }); const asBuiltDrawings = dedupeByKey( extractArrayData(asBuiltDrawingsData), (drawing) => drawing.currentRevisionPublicId ?? drawing.currentRevision?.publicId ?? drawing.publicId ); const selectedDisciplineId = watch('disciplineId'); const rfaTypeId = watch('rfaTypeId'); const disciplineId = watch('disciplineId'); const toOrganizationId = watch('toOrganizationId'); const selectedShopDrawingRevisionIds = watch('shopDrawingRevisionIds') ?? []; const selectedAsBuiltDrawingRevisionIds = watch('asBuiltDrawingRevisionIds') ?? []; const selectedRfaType = rfaTypes.find((rfaType) => getMasterOptionValue(rfaType) === rfaTypeId); const selectedRfaTypeCode = selectedRfaType?.typeCode?.toUpperCase(); const requiresShopDrawings = selectedRfaTypeCode === 'DDW' || selectedRfaTypeCode === 'SDW'; const requiresAsBuiltDrawings = selectedRfaTypeCode === 'ADW'; useEffect(() => { // Reset page and search when project changes setShopDrawingPage(1); setShopDrawingSearch(''); setAsBuiltDrawingPage(1); setAsBuiltDrawingSearch(''); if (requiresShopDrawings) { setValue('asBuiltDrawingRevisionIds', []); clearErrors('asBuiltDrawingRevisionIds'); return; } if (requiresAsBuiltDrawings) { setValue('shopDrawingRevisionIds', []); clearErrors('shopDrawingRevisionIds'); return; } setValue('shopDrawingRevisionIds', []); setValue('asBuiltDrawingRevisionIds', []); clearErrors('shopDrawingRevisionIds'); clearErrors('asBuiltDrawingRevisionIds'); }, [requiresShopDrawings, requiresAsBuiltDrawings, selectedProjectId, setValue, clearErrors]); // -- Preview Logic -- const [preview, setPreview] = useState<{ number: string; isDefaultTemplate: boolean } | null>(null); useEffect(() => { if (!selectedProjectId || !rfaCorrespondenceType?.publicId || !rfaTypeId || !disciplineId || !toOrganizationId) { setPreview(null); return; } const fetchPreview = async () => { try { const res = await correspondenceService.previewNumber({ projectId: selectedProjectId, typeId: rfaCorrespondenceType.publicId, disciplineId, recipients: [{ organizationId: toOrganizationId, type: 'TO' }], subject: watch('subject') || 'Preview Subject', }); setPreview(res); } catch (_err) { setPreview(null); } }; const timer = setTimeout(fetchPreview, 500); return () => clearTimeout(timer); }, [rfaTypeId, disciplineId, toOrganizationId, selectedProjectId, rfaCorrespondenceType?.publicId, watch]); const onSubmit = (data: RFAFormData) => { if (requiresShopDrawings && data.shopDrawingRevisionIds?.length === 0) { setError('shopDrawingRevisionIds', { type: 'manual', message: 'Please select at least one Shop Drawing Revision', }); return; } if (requiresAsBuiltDrawings && data.asBuiltDrawingRevisionIds?.length === 0) { setError('asBuiltDrawingRevisionIds', { type: 'manual', message: 'Please select at least one As-Built Drawing Revision', }); return; } clearErrors('shopDrawingRevisionIds'); clearErrors('asBuiltDrawingRevisionIds'); const payload: CreateRfaDto = { ...data, shopDrawingRevisionIds: requiresShopDrawings ? data.shopDrawingRevisionIds : undefined, asBuiltDrawingRevisionIds: requiresAsBuiltDrawings ? data.asBuiltDrawingRevisionIds : undefined, }; createMutation.mutate(payload, { onSuccess: () => { router.push('/rfas'); }, }); }; const onInvalidSubmit: SubmitErrorHandler = () => undefined; const submitForm = handleSubmit(onSubmit, onInvalidSubmit); const handleFormSubmit = (event: FormEvent) => { void submitForm(event).catch(() => undefined); }; return (
{preview && (

Document Number Preview

{preview.number} {preview.isDefaultTemplate && ( Default Template )}
)}

RFA Information

{errors.projectId &&

{errors.projectId.message}

}
{errors.contractId &&

{errors.contractId.message}

}
{errors.disciplineId &&

{errors.disciplineId.message}

}
{errors.rfaTypeId &&

{errors.rfaTypeId.message}

}
{errors.toOrganizationId && (

{errors.toOrganizationId.message}

)}
{errors.subject &&

{errors.subject.message}

}