page.tsx "use client" import { useState,useEffect } from "react" import { FaFileAlt, FaUpload, FaDollarSign, FaUser, FaHome, FaCheckCircle, FaPlus, FaSearch } from "react-icons/fa" import Link from "next/link" import Modal from "../../components/Modal" import CustomerProfile from "./customer_profile" import AsyncSelect, { type Option as AsyncOption } from "../../components/AsyncSelect" import api from "@/lib/axios" import { renderError } from "@/utils/renderError" import { handleApiError } from "@/utils/handleApiError" import { AnimatedButton } from "@/app/components/Button" import DatePicker from "react-datepicker" import "react-datepicker/dist/react-datepicker.css" export default function SimpleForm() { const [formData, setFormData] = useState({ // Client Details person: null as AsyncOption | null, addNew: "", browseClients: "", discount: "", // Property Details plotNoFlat: null as AsyncOption | null, salesDocument: "", openedNo: "", area: "", parking: "", grossArea: "", // Booking Details price: 0, bookingDiscount: 0, requiredCharacterLimit: "", netPrice: 0, requiredPercentage: 0, vatType: "", vatPercentage: 0, vatAmount: 0, extraCost: 0, vatPaymentTerms: "", netPayable: 0, bookingDate: null as Date | null, oldCharges: 0, adminFee: 0, // Reference Source referenceSource: "", uploadDocument: null, // Text Areas remarks: "", paymentTerm: "", // Internal Agents internalAgent: null as AsyncOption | null, internalAgentCommission: 0, others: false, //External Agents externalAgent: null as AsyncOption | null, externalAgentCommission: 0, other: false, // Check List (moved to separate state: checkTypes) }) const [showCustomerModal, setShowCustomerModal] = useState(false) const [errors, setErrors] = useState<{ [key: string]: string[] }>({}) const [unitDetails, setUnitDetails] = useState(null) const [open,setOpen]=useState(false) const [checkTypes, setCheckTypes] = useState>([]) const handleChange = (e: any) => { const { name, value, files, type, checked } = e.target setFormData({ ...formData, [name]: files ? files[0] : type === "checkbox" ? checked : value, }) } const handleCheckListChange = (id: number, field: 'completed' | 'status', value: any) => { setCheckTypes((prev) => prev.map((c) => (c.id === id ? { ...c, [field]: field === 'completed' ? Boolean(value) : String(value) } : c))) } // Load checklist types on first render useEffect(() => { (async () => { try { const res: any = await api.get('/bookings/checks') const payload = res?.data ?? res const arr: any[] = Array.isArray(payload?.data) ? payload.data : (Array.isArray(payload) ? payload : []) const normalized = arr.map((it: any) => ({ id: Number(it.id), text: String(it.text || it.checkname || ''), completed: false, status: 'NO' })) setCheckTypes(normalized) } catch (err: any) { handleApiError({ err, setErrors }) } })() }, []) // Client-side validation for required fields const validateForm = () => { const next: { [key: string]: string[] } = {} const clientId = (formData.person as any)?.value const unitId = (formData.plotNoFlat as any)?.value if (!clientId) next.client_id = ["Client is required"] if (!unitId) next.unit_id = ["Plot/Flat is required"] if (!formData.bookingDate) next.booking_date = ["Booking date is required"] setErrors(next) return Object.keys(next).length === 0 } const buildBookingPayload = () => { const payload: any = { ...formData } // Map select objects to backend ids payload.client_id = (formData.person as any)?.value payload.unit_id = (formData.plotNoFlat as any)?.value payload.booking_date = formData.bookingDate ? formData.bookingDate.toISOString().split('T')[0] : "" // Coerce numeric fields payload.sell_price = Number(unitDetails?.sale_price ?? unitDetails?.price ?? formData.price ?? 0) payload.net_price = Number(formData.netPrice ?? 0) payload.net_payable = Number(formData.netPayable ?? 0) payload.discount = Number((formData as any).bookingDiscount ?? (formData as any).discount ?? 0) payload.liability = Number(formData.requiredPercentage ?? 0) // Convert area fields to numbers payload.area = formData.area ? Number(formData.area) : null payload.gross_area = formData.grossArea ? Number(formData.grossArea) : null // Handle parking field (convert YES/NO to boolean or keep as string based on backend needs) payload.parking = formData.parking || null // IDs from selected unit details when available if (unitDetails) { payload.floor_id = unitDetails.floor_id ?? unitDetails.floor?.id ?? payload.floor_id payload.munit_id = unitDetails.munit_id ?? unitDetails.munit?.id ?? payload.munit_id payload.saletype_id = unitDetails.saletype_id ?? unitDetails.saletype?.id ?? payload.saletype_id payload.unittypedetail = unitDetails.unittypedetail ?? payload.unittypedetail payload.number = payload.number ?? unitDetails.number // Optional convenient fields payload.area = payload.area || unitDetails.area payload.gross_area = payload.gross_area || unitDetails.gross_area || unitDetails.grossArea } // Map opened number to expected key if still missing if (!payload.number && (formData as any).openedNo) payload.number = (formData as any).openedNo // Parking must be boolean const parkingValue: any = (formData as any).parking let parkingBool = false if (typeof parkingValue === 'boolean') parkingBool = parkingValue else if (typeof parkingValue === 'string') { const v = parkingValue.trim().toLowerCase() parkingBool = v === 'yes' || v === 'true' || v === '1' } payload.parking = parkingBool // Ensure items key exists; backend may require it if (payload.items === undefined) payload.items = [] // Build required arrays const clientId = (formData.person as any)?.value if (clientId) { payload.items = [{ client_id: Number(clientId) }] } payload.checklistitems = (checkTypes || []).map((c) => ({ checktype_id: Number(c.id), completed: Boolean(c.completed), })) // Remove frontend-only fields delete payload.person delete payload.plotNoFlat delete payload.bookingDate // UI-only keys delete payload.checkList return payload } const handleSave = async() => { setErrors({}) if (!validateForm()) return try { const payload = buildBookingPayload() const res: any = await api.post("/bookings/store", payload) console.log("Saving form data:", payload) alert((res as any)?.message || (res as any)?.data?.message || "Saved successfully") } catch (err: any) { handleApiError({ err, setErrors }) } } const handleSaveAndNew = async() => { setErrors({}) if (!validateForm()) return try { const payload = buildBookingPayload() const res = await api.post("/bookings/store", payload) } catch (err: any) { handleApiError({ err, setErrors }) return } setFormData({ person: null, addNew: "", browseClients: "", discount: "", plotNoFlat: null, salesDocument: "", openedNo: "", area: "", parking: "", grossArea: "", price: 0, bookingDiscount: 0, requiredCharacterLimit: "", netPrice: 0, requiredPercentage: 0, vatType: "", vatPercentage: 0, vatAmount: 0, extraCost: 0, vatPaymentTerms: "", netPayable: 0, bookingDate: null as Date | null, oldCharges: 0, adminFee: 0, referenceSource: "", uploadDocument: null, remarks: "", paymentTerm: "", internalAgent: null as AsyncOption | null, internalAgentCommission: 0, others: false, externalAgent: null as AsyncOption | null, externalAgentCommission: 0, other: false, }) setCheckTypes((prev) => prev.map((c) => ({ ...c, completed: false, status: 'NO' }))) } const handleCancel = () => { if (confirm("Are you sure you want to cancel? All unsaved changes will be lost.")) { setFormData({ person: null, addNew: "", browseClients: "", discount: "", plotNoFlat: null, salesDocument: "", openedNo: "", area: "", parking: "", grossArea: "", price: 0, bookingDiscount: 0, requiredCharacterLimit: "", netPrice: 0, requiredPercentage: 0, vatType: "", vatPercentage: 0, vatAmount: 0, extraCost: 0, vatPaymentTerms: "", netPayable: 0, bookingDate: null as Date | null, oldCharges: 0, adminFee: 0, referenceSource: "", uploadDocument: null, remarks: "", paymentTerm: "", internalAgent: null as AsyncOption | null, internalAgentCommission: 0, others: false, externalAgent: null as AsyncOption | null, externalAgentCommission: 0, other: false, }) setCheckTypes((prev) => prev.map((c) => ({ ...c, completed: false, status: 'NO' }))) } } return (
{/* Header */}

Create Booking

Fill out the booking form details

setShowCustomerModal(true)} variant="primary" className="px-6 py-2"> Add New {/* Browse Clients */}
{/* Client Details */}

Client Details

(Required)
{ const option = (opt as AsyncOption) ?? null setFormData({ ...formData, person: option, }) }} minInputLength={1} fetchWhenEmpty={true} disableSearchRequests={false} className="w-full" /> {renderError("client_id", errors)}
{/* Property Details */}

Property Details

{ const option = (opt as AsyncOption) ?? null setFormData({ ...formData, plotNoFlat: option, }) // Fetch selected unit details and set price try { const id = option?.value if (id) { const res: any = await api.get(`/units/show/${id}`) // Try common structures: {data: {...}}, or direct object const payload = res?.data ?? res const unit = payload?.data ?? payload const price = unit?.price ?? unit?.unit_price ?? unit?.sale_price ?? 0 const area = unit?.area const grossArea = unit?.gross_area ?? unit?.grossArea const parking = unit?.parking const netPrice = unit?.net_price ?? unit?.netPrice ?? price const adminFee = unit?.admin_fee ?? unit?.adminFee const bookingDateStr = unit?.booking_date ?? unit?.bookingDate setFormData((prev) => ({ ...prev, price: Number(price) || 0, netPrice: Number(netPrice) || 0, area: area != null ? String(area) : prev.area, grossArea: grossArea != null ? String(grossArea) : prev.grossArea, parking: typeof parking === 'number' ? (parking === 1 ? 'YES' : 'NO') : (typeof parking === 'boolean' ? (parking ? 'YES' : 'NO') : (prev.parking || '')), adminFee: Number(adminFee ?? prev.adminFee) || 0, bookingDate: bookingDateStr ? new Date(bookingDateStr) : prev.bookingDate, })) setUnitDetails(unit) } } catch (e) { // ignore fetch error for UX; keep selection } }} className="w-full" minInputLength={1} fetchWhenEmpty={true} disableSearchRequests={false} /> {renderError("unit_id", errors)}
{/* Booking Details */}

Booking Details

{renderError("price", errors)}
{renderError("vatType", errors)}
{renderError("vatPercentage", errors)}
{renderError("vatAmount", errors)}
{renderError("netPayable", errors)}
setFormData(prev => ({ ...prev, bookingDate: date }))} dateFormat="yyyy-MM-dd" className="w-full bg-white/10 backdrop-blur-xl border border-white/20 py-2.5 px-3 rounded-lg text-white text-sm focus:outline-none focus:ring-1 focus:ring-amber-400/50 focus:border-amber-400/50" placeholderText="Select booking date" showPopperArrow={false} /> {renderError("booking_date", errors)}
{/* Reference Source */}

Reference Source (Optional) NEW

{renderError("referenceSource", errors)}

Choose File

NO FILE CHOSEN

{renderError("uploadDocument", errors)}
{/* Remarks and Payment Term */}