"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"; 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.number().min(1, "Discipline is required"), rfaTypeId: z.number().min(1, "Type is required"), 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 = { uuid?: string; id?: number; projectName?: string; projectCode?: string; }; type ContractOption = { uuid?: string; id?: number; contractName?: string; name?: string; contractCode?: string; }; type DisciplineOption = { id: number; disciplineCode: string; codeNameEn?: string; codeNameTh?: string; }; type RfaTypeOption = { id: number; typeCode?: string; typeName?: string; typeNameEn?: string; typeNameTh?: string; }; type CorrespondenceTypeOption = { id: number; typeCode?: string; typeName?: string; }; type OrganizationOption = { uuid?: string; id?: number; organizationCode?: string; organizationName?: string; }; type SelectableDrawingOption = { uuid?: string; drawingNumber?: string; title?: string; legacyDrawingNumber?: string; currentRevisionUuid?: string; currentRevision?: { uuid?: 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); }; export function RFAForm() { const router = useRouter(); const createMutation = useCreateRFA(); const { data: projectsData, isLoading: isLoadingProjects } = useProjects(); const projects = dedupeByKey( extractArrayData(projectsData), (project) => project.uuid ?? project.id ); const { data: organizationsData, isLoading: isLoadingOrganizations } = useOrganizations({ isActive: true }); const organizations = dedupeByKey( extractArrayData(organizationsData), (organization) => organization.uuid ?? organization.id ); 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: 0, rfaTypeId: 0, 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.uuid ?? contract.id ); const selectedContractId = watch("contractId"); const { data: disciplinesData, isLoading: isLoadingDisciplines } = useDisciplines(selectedContractId); const disciplines = dedupeByKey(extractArrayData(disciplinesData), (discipline) => discipline.id); const { data: rfaTypesData, isLoading: isLoadingRfaTypes } = useRfaTypes(selectedContractId); const rfaTypes = dedupeByKey(extractArrayData(rfaTypesData), (rfaType) => rfaType.id); 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.currentRevisionUuid ?? drawing.currentRevision?.uuid ?? drawing.uuid ); 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.currentRevisionUuid ?? drawing.currentRevision?.uuid ?? drawing.uuid ); 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) => rfaType.id === 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?.id || !rfaTypeId || !disciplineId || !toOrganizationId) { setPreview(null); return; } const fetchPreview = async () => { try { const res = await correspondenceService.previewNumber({ projectId: selectedProjectId, typeId: rfaCorrespondenceType.id, 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?.id, 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.subject && (

{errors.subject.message}

)}