import { Button, FormControlLabel, Grid, InputAdornment, Switch, TextField, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Select, MenuItem, IconButton } from '@mui/material'; import MainCard from "../../../components/MainCard"; import * as React from "react"; import {useForm} from "react-hook-form"; import {useContext, useEffect, useState} from "react"; import LoadingComponent from "../../extra-pages/LoadingComponent"; import {useLocation, useNavigate, useParams} from "react-router-dom"; import { GeneralConfirmWindow, getComboValueByIdList, getComboValueByLabel, getDateString, getDeletedRecordWithRefList, getIdList, getNewRecordWithRefList, isOptionEqualToValue, isStringEmptyAfterTrim, notifyDeleteError, notifyDeleteSuccess, notifySaveSuccess, trimDataBeforePost, } from "../../../utils/CommonFunction"; import Autocomplete from "@mui/material/Autocomplete"; import axios from "axios"; import {apiPath} from "../../../auth/utils"; import { CHECK_EVENT_DUPLICATE, GET_CLIENT_PATH, GET_CONSULTANT_COMBO_LIST, GET_SUB_DIVISION_COMBO_LIST, POST_CLIENT_PATH } from "../../../utils/ApiPathConst"; import {LIONER_BUTTON_THEME, LIONER_LONG_BUTTON_THEME, GENERAL_RED_COLOR} from "../../../themes/colorConst"; import {eventFrequencyCombo, EVENT_REGION_COMBO, EVENT_TYPE_COMBO} from "../../../utils/ComboConst"; import {DatePicker} from "@mui/x-date-pickers/DatePicker"; import dayjs from "dayjs"; import {DemoItem} from "@mui/x-date-pickers/internals/demo"; import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"; import {LocalizationProvider} from "@mui/x-date-pickers/LocalizationProvider"; import {ThemeProvider} from "@emotion/react"; import UploadContext from "../../../components/UploadProvider"; import {isObjEmpty} from "../../../utils/Utils"; import AbilityContext from "../../../components/AbilityProvider"; import {CARD_MAX_WIDTH} from "../../../themes/themeConst"; const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { const location = useLocation(); const queryParams = new URLSearchParams(location.search); const refId = queryParams.get("refId"); const params = useParams(); const navigate = useNavigate(); const ability = useContext(AbilityContext); const [onReady, setOnReady] = useState(false); const [errors, setErrors] = useState({}); const [clientDetail, setClientDetail] = useState({}); const [isCollectData, setIsCollectData] = useState(false); const [userConfirm, setUserConfirm] = useState(false); const [isEditing, setIsEditing] = useState(false); const { setIsUploading } = useContext(UploadContext); const [refClient, setRefClient] = useState({}); const [consultantComboList, setConsultantComboList] = useState([]); const [selectedConsultant, setSelectedConsultant] = useState(null); const [isFirstInit, setIsFirstInit] = useState(true); // Form data const { register, getValues, setValue } = useForm(); const [dob, setDob] = useState(null); const [dobError, setDobError] = React.useState(null); // Handler to navigate back const returnSearchPage = () => { navigate('/client'); } const FIXED_HEADER_HEIGHT = 120; // <-- **IMPORTANT: Set this to the height of your actual fixed header in pixels** const handleFocus = (event) => { setTimeout(() => { const element = event.target; // Get the element's position relative to the viewport const rect = element.getBoundingClientRect(); // Calculate the target scroll position (Current scroll position + element top position - Header height) const targetScrollY = window.scrollY + rect.top - FIXED_HEADER_HEIGHT; // Scroll the window to the new position window.scrollTo({ top: targetScrollY, behavior: 'smooth' }); // Ensure the element has focus element.focus(); }, 100); }; // Handler to enable edit mode const handleEditClick = () => { setIsEditing(true); }; useEffect(() => { if (!isObjEmpty(refClient) && consultantComboList.length > 0 && isFirstInit) { const matched = consultantComboList.find(c => c.id === refClient.consultantId); setSelectedConsultant(matched || null); setValue("consultantId", matched || null); setIsFirstInit(false); } }, [refClient, consultantComboList, isFirstInit]); // 1. Earned Income const [earnedIncome, setEarnedIncome] = useState({ salaryCurrent: '', salaryLast: '', bonusCurrent: '', bonusLast: '', otherEarnedCurrent: '', otherEarnedLast: '' }); const handleEarnedIncomeChange = (field, value) => { setEarnedIncome(prev => ({ ...prev, [field]: value })); }; const totalEarnedCurrent = (parseFloat(earnedIncome.salaryCurrent) || 0) + (parseFloat(earnedIncome.bonusCurrent) || 0) + (parseFloat(earnedIncome.otherEarnedCurrent) || 0); const totalEarnedLast = (parseFloat(earnedIncome.salaryLast) || 0) + (parseFloat(earnedIncome.bonusLast) || 0) + (parseFloat(earnedIncome.otherEarnedLast) || 0); // 2. Unearned Income (Updated for Current Year / Last Year columns as per Image 2) const [unearnedIncome, setUnearnedIncome] = useState({ companyInterestCurrent: '', companyInterestLast: '', dividendsCurrent: '', dividendsLast: '', rentalsCurrent: '', rentalsLast: '', investmentIncomeCurrent: '', investmentIncomeLast: '', otherUnearnedCurrent: '', otherUnearnedLast: '' }); const handleUnearnedIncomeChange = (field, value) => { setUnearnedIncome(prev => ({ ...prev, [field]: value })); }; const totalUnearnedCurrent = (parseFloat(unearnedIncome.companyInterestCurrent) || 0) + (parseFloat(unearnedIncome.dividendsCurrent) || 0) + (parseFloat(unearnedIncome.rentalsCurrent) || 0) + (parseFloat(unearnedIncome.investmentIncomeCurrent) || 0) + (parseFloat(unearnedIncome.otherUnearnedCurrent) || 0); const totalUnearnedLast = (parseFloat(unearnedIncome.companyInterestLast) || 0) + (parseFloat(unearnedIncome.dividendsLast) || 0) + (parseFloat(unearnedIncome.rentalsLast) || 0) + (parseFloat(unearnedIncome.investmentIncomeLast) || 0) + (parseFloat(unearnedIncome.otherUnearnedLast) || 0); // Total Income Calculation const totalIncomeCurrent = totalEarnedCurrent + totalUnearnedCurrent; const totalIncomeLast = totalEarnedLast + totalUnearnedLast; // 3. Expenditure Statement const [expenditure, setExpenditure] = useState({ mortgageCurrent: '', mortgageLast: '', rentCurrent: '', rentLast: '', schoolingCurrent: '', schoolingLast: '', membershipsCurrent: '', membershipsLast: '', otherExpenditureCurrent: '', otherExpenditureLast: '', interestCurrent: '', interestLast: '', principalCurrent: '', principalLast: '', otherPrefFinanceCurrent: '', otherPrefFinanceLast: '' }); const handleExpenditureChange = (field, value) => { setExpenditure(prev => ({ ...prev, [field]: value })); }; const totalExpenditureCurrent = (parseFloat(expenditure.mortgageCurrent) || 0) + (parseFloat(expenditure.rentCurrent) || 0) + (parseFloat(expenditure.schoolingCurrent) || 0) + (parseFloat(expenditure.membershipsCurrent) || 0) + (parseFloat(expenditure.otherExpenditureCurrent) || 0) + (parseFloat(expenditure.interestCurrent) || 0) + (parseFloat(expenditure.principalCurrent) || 0)+ (parseFloat(expenditure.otherPrefFinanceCurrent) || 0); const totalExpenditureLast = (parseFloat(expenditure.mortgageLast) || 0) + (parseFloat(expenditure.rentLast) || 0) + (parseFloat(expenditure.schoolingLast) || 0) + (parseFloat(expenditure.membershipsLast) || 0) + (parseFloat(expenditure.otherExpenditureLast) || 0) + (parseFloat(expenditure.interestLast) || 0) + (parseFloat(expenditure.principalLast) || 0)+ (parseFloat(expenditure.otherPrefFinanceLast) || 0); // 4. Liquid & Non-Liquid Assets (Combined for Image 4) const [assets, setAssets] = useState({ // Liquid Assets cashDepositsCurrent: '', cashDepositsLast: '', investmentsCurrent: '', investmentsLast: '', otherLiquidCurrent: '', otherLiquidLast: '', // Non-Liquid Assets netBusinessInterestCurrent: '', netBusinessInterestLast: '', personalPropertiesCurrent: '', personalPropertiesLast: '', realEstateCurrent: '', realEstateLast: '', otherNonLiquidCurrent: '', otherNonLiquidLast: '' }); const handleAssetsChange = (field, value) => { setAssets(prev => ({ ...prev, [field]: value })); }; const totalLiquidAssetCurrent = (parseFloat(assets.cashDepositsCurrent) || 0) + (parseFloat(assets.investmentsCurrent) || 0) + (parseFloat(assets.otherLiquidCurrent) || 0); const totalLiquidAssetLast = (parseFloat(assets.cashDepositsLast) || 0) + (parseFloat(assets.investmentsLast) || 0) + (parseFloat(assets.otherLiquidLast) || 0); const totalNonLiquidAssetCurrent = (parseFloat(assets.netBusinessInterestCurrent) || 0) + (parseFloat(assets.personalPropertiesCurrent) || 0) + (parseFloat(assets.realEstateCurrent) || 0) + // ← NEW (parseFloat(assets.otherNonLiquidCurrent) || 0); const totalNonLiquidAssetLast = (parseFloat(assets.netBusinessInterestLast) || 0) + (parseFloat(assets.personalPropertiesLast) || 0) + (parseFloat(assets.realEstateLast) || 0) + // ← NEW (parseFloat(assets.otherNonLiquidLast) || 0); const totalAssetsCurrent = totalLiquidAssetCurrent + totalNonLiquidAssetCurrent; const totalAssetsLast = totalLiquidAssetLast + totalNonLiquidAssetLast; // 5. Liabilities (Combined with Existing Premium Financing/Policy Loan section from Image 5) const [liabilities, setLiabilities] = useState({ personalLoansCurrent: '', personalLoansLast: '', mortgagesCurrent: '', mortgagesLast: '', marginAccountCurrent: '', marginAccountLast: '', loanGuaranteesCurrent: '', loanGuaranteesLast: '', bankingFacilityCurrent: '', bankingFacilityLast: '', // Existing Premium Financing/Policy Loan (Principal and Other from Image 5) prefFinancePrincipalCurrent: '', prefFinancePrincipalLast: '', prefFinanceOtherCurrent: '', prefFinanceOtherLast: '', }); const handleLiabilitiesChange = (field, value) => { setLiabilities(prev => ({ ...prev, [field]: value })); }; const totalLiabilitiesCurrent = (parseFloat(liabilities.personalLoansCurrent) || 0) + (parseFloat(liabilities.mortgagesCurrent) || 0) + (parseFloat(liabilities.marginAccountCurrent) || 0) + (parseFloat(liabilities.loanGuaranteesCurrent) || 0) + (parseFloat(liabilities.bankingFacilityCurrent) || 0) + (parseFloat(liabilities.prefFinancePrincipalCurrent) || 0) + (parseFloat(liabilities.prefFinanceOtherCurrent) || 0); const totalLiabilitiesLast = (parseFloat(liabilities.personalLoansLast) || 0) + (parseFloat(liabilities.mortgagesLast) || 0) + (parseFloat(liabilities.marginAccountLast) || 0) + (parseFloat(liabilities.loanGuaranteesLast) || 0) + (parseFloat(liabilities.bankingFacilityLast) || 0) + (parseFloat(liabilities.prefFinancePrincipalLast) || 0) + (parseFloat(liabilities.prefFinanceOtherLast) || 0); const dobErrorMessage = React.useMemo(() => { switch (dobError) { case 'invalidDate': { return "Invalid date"; } } }, [dobError]); // DELETE WINDOW RELATED const [isWindowOpen, setIsWindowOpen] = React.useState(false); const handleClose = () => { setIsWindowOpen(false); }; const handleDeleteClick = () => { setIsWindowOpen(true); }; const copyClientAsNew = () => { navigate(`/event/maintain/-1?refId=${params.id}`); }; const formatNumberForDisplay = (num) => { console.log(num); // 1. Handle null, undefined, or empty string input gracefully (return as is) if (num === null || num === undefined || num === '') return ''; // Convert to a string and use regex to allow up to one decimal point and digits const numString = String(num); // Check if the input ends with a decimal point (e.g., '100.') // We preserve this during display so the user can continue typing decimals. const endsWithDecimal = numString.endsWith('.'); // Attempt to parse the number const numericValue = parseFloat(numString); // 2. If it's NaN (like if the input was only '.'), return the original string if (isNaN(numericValue) && numString !== '.') return numString; // 3. Use Intl.NumberFormat to add commas const formatted = new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, maximumFractionDigits: 2, }).format(numericValue); // 4. If the original string ended with a decimal (e.g., '123.'), // re-append the decimal point because Intl.NumberFormat removes it for whole numbers. if (endsWithDecimal) { // If the formatted number doesn't already contain a decimal point if (!formatted.includes('.')) { return formatted + '.'; } } console.log(formatted); return formatted; }; // Function to clean the string input back to a raw number string for state storage const cleanNumberForState = (inputString) => { if (inputString === null || inputString === undefined) return ''; // 1. Remove commas (,) and any thousand separators let cleanedString = inputString.toString().replace(/,/g, ''); // 2. Check for multiple decimal points (only keep the first one) const parts = cleanedString.split('.'); if (parts.length > 2) { // If there are multiple decimals, we revert to the state before the last one was typed // This prevents '123.45.' from breaking the number return parts[0] + '.' + parts.slice(1).join(''); } // 3. Optional: Remove characters that are NOT digits or a decimal point // cleanedString = cleanedString.replace(/[^0-9.]/g, ''); // Return the cleaned string. We keep it as a STRING to preserve leading zeros // and prevent immediate number conversion issues. return cleanedString; }; function updateData() { axios.delete(`${apiPath}${GET_CLIENT_PATH}/${params.id}`) .then((response) => { if (response.status === 204) { notifyDeleteSuccess(); setIsWindowOpen(false); returnSearchPage(); } }) .catch(error => { console.log(error); return false; }); } function getConsultantCombo() { axios.get(`${apiPath}${GET_CONSULTANT_COMBO_LIST}`, { params: {} }) .then((response) => { if (response.status === 200) { setConsultantComboList(response.data.records); } }) .catch(error => { console.log(error); return false; }); } useEffect(() => { if (!isNewRecord) { setRefClient(refClientDetail); } getConsultantCombo(); }, []); useEffect(() => { if (!isObjEmpty(refClient)) { setValue("lastname", refClient.lastname); setValue("firstname", refClient.firstname); setValue("email", refClient.email); setValue("phone1", refClient.phone1); setValue("phone1Code", refClient.phone1Code); setValue("phone2", refClient.phone2); setValue("remarks", refClient.remarks); setValue("caseManagerId", 1); setValue("crAddressRoom", refClient.crAddressRoom); setValue("crAddressFloor", refClient.crAddressFloor); setValue("crAddressBuilding", refClient.crAddressBuilding); setValue("crAddressStreet", refClient.crAddressStreet); setValue("crAddressStreet", refClient.crAddressStreet); setValue("crAddressArea", refClient.crAddressArea); setValue("crAddressCity", refClient.crAddressCity); setValue("crAddressCountry", refClient.crAddressCountry); setValue("crAddressPostalCode", refClient.crAddressPostalCode); setValue("corAddressRoom", refClient.corAddressRoom); setValue("corAddressFloor", refClient.corAddressFloor); setValue("corAddressBlock", refClient.corAddressBlock); setValue("corAddressBuilding", refClient.corAddressBuilding); setValue("corAddressStreet", refClient.corAddressStreet); setValue("corAddressArea", refClient.corAddressArea); setValue("corAddressCity", refClient.corAddressCity); setValue("corAddressCountry", refClient.corAddressCountry); setValue("corAddressPostalCode", refClient.corAddressPostalCode); earnedIncome.salaryCurrent = refClient.salaryCurrent; earnedIncome.salaryLast = refClient.salaryLast; earnedIncome.bonusCurrent = refClient.bonusCurrent; earnedIncome.bonusLast = refClient.bonusLast; earnedIncome.otherEarnedCurrent = refClient.otherEarnedCurrent; earnedIncome.otherEarnedLast = refClient.otherEarnedLast; unearnedIncome.companyInterestCurrent = refClient.companyInterestCurrent; unearnedIncome.companyInterestLast = refClient.companyInterestLast; unearnedIncome.dividendsCurrent = refClient.dividendsCurrent; unearnedIncome.dividendsLast = refClient.dividendsLast; unearnedIncome.rentalsCurrent = refClient.rentalsCurrent; unearnedIncome.rentalsLast = refClient.rentalsLast; unearnedIncome.investmentIncomeCurrent = refClient.investmentIncomeCurrent; unearnedIncome.investmentIncomeLast = refClient.investmentIncomeLast; unearnedIncome.otherUnearnedCurrent = refClient.otherUnearnedCurrent; unearnedIncome.otherUnearnedLast = refClient.otherUnearnedLast; expenditure.mortgageCurrent = refClient.mortgageCurrent; expenditure.mortgageLast = refClient.mortgageLast; expenditure.rentCurrent = refClient.rentCurrent; expenditure.rentLast = refClient.rentLast; expenditure.schoolingCurrent = refClient.schoolingCurrent; expenditure.schoolingLast = refClient.schoolingLast; expenditure.membershipsCurrent = refClient.membershipsCurrent; expenditure.membershipsLast = refClient.membershipsLast; expenditure.otherExpenditureCurrent = refClient.otherExpenditureCurrent; expenditure.otherExpenditureLast = refClient.otherExpenditureLast; expenditure.interestCurrent = refClient.interestCurrent; expenditure.interestLast = refClient.interestLast; expenditure.principalCurrent = refClient.principalCurrent; expenditure.principalLast = refClient.principalLast; expenditure.otherPrefFinanceCurrent = refClient.otherPrefFinanceCurrent; expenditure.otherPrefFinanceLast = refClient.otherPrefFinanceLast; assets.cashDepositsCurrent = refClient.cashDepositsCurrent; assets.cashDepositsLast = refClient.cashDepositsLast; assets.investmentsCurrent = refClient.investmentsCurrent; assets.investmentsLast = refClient.investmentsLast; assets.otherLiquidCurrent = refClient.otherLiquidCurrent; assets.otherLiquidLast = refClient.otherLiquidLast; assets.netBusinessInterestCurrent = refClient.netBusinessInterestCurrent; assets.netBusinessInterestLast = refClient.netBusinessInterestLast; assets.personalPropertiesCurrent = refClient.personalPropertiesCurrent; assets.personalPropertiesLast = refClient.personalPropertiesLast; assets.realEstateCurrent = refClient.realEstateCurrent; assets.realEstateLast = refClient.realEstateLast; assets.otherNonLiquidCurrent = refClient.otherNonLiquidCurrent; assets.otherNonLiquidLast = refClient.otherNonLiquidLast; liabilities.personalLoansCurrent = refClient.personalLoansCurrent; liabilities.personalLoansLast = refClient.personalLoansLast; liabilities.mortgagesCurrent = refClient.mortgagesCurrent; liabilities.mortgagesLast = refClient.mortgagesLast; liabilities.marginAccountCurrent = refClient.marginAccountCurrent; liabilities.marginAccountLast = refClient.marginAccountLast; liabilities.loanGuaranteesCurrent = refClient.loanGuaranteesCurrent; liabilities.loanGuaranteesLast = refClient.loanGuaranteesLast; liabilities.bankingFacilityCurrent = refClient.bankingFacilityCurrent; liabilities.bankingFacilityLast = refClient.bankingFacilityLast; liabilities.prefFinancePrincipalCurrent = refClient.prefFinancePrincipalCurrent; liabilities.prefFinancePrincipalLast = refClient.prefFinancePrincipalLast; liabilities.prefFinanceOtherCurrent = refClient.prefFinanceOtherCurrent; liabilities.prefFinanceOtherLast = refClient.prefFinanceOtherLast; //totalEarnedCurrent = refClient.totalEarnedCurrent; //totalEarnedLast = refClient.totalEarnedLast; // Set consultantId for Autocomplete const selectedConsultant = consultantComboList.find( (option) => option.id === refClient.consultantId ); setValue("consultantId", selectedConsultant || null); setDob(dayjs(getDateString(refClient.dob))); } }, [refClient, consultantComboList]); useEffect(() => { if (!isObjEmpty(refClient)) { setOnReady(true); } else if (isNewRecord) { setOnReady(true); setIsEditing(true); } }, [refClient]); useEffect(() => { if (isCollectData) { const values = getValues(); const formErrors = {}; if (isStringEmptyAfterTrim(values.lastname)) { formErrors.lastname = 'Last Name is required'; } setErrors(formErrors); if (Object.keys(formErrors).length === 0) { let data = {}; data["id"] = isNewRecord ? params.id : refClientDetail.id; data["lastname"] = values.lastname; data["firstname"] = values.firstname; data["email"] = values.email; data["phone1"] = values.phone1; data["phone1Code"] = values.phone1Code; data["phone2"] = values.phone2; data["remarks"] = values.remarks; data["caseManagerId"] = 1; data["consultantId"] = values.consultantId ? values.consultantId.id : null; data["dob"] = dob === null ? null : dayjs(dob).format('YYYY-MM-DD'); data["crAddressRoom"] = values.crAddressRoom; data["crAddressFloor"] = values.crAddressFloor; data["crAddressBlock"] = values.crAddressBlock; data["crAddressBuilding"] = values.crAddressBuilding; data["crAddressStreet"] = values.crAddressStreet; data["crAddressArea"] = values.crAddressArea; data["crAddressCity"] = values.crAddressCity; data["crAddressCountry"] = values.crAddressCountry; data["crAddressPostalCode"] = values.crAddressPostalCode; data["corAddressRoom"] = values.corAddressRoom; data["corAddressFloor"] = values.corAddressFloor; data["corAddressBlock"] = values.corAddressBlock; data["corAddressBuilding"] = values.corAddressBuilding; data["corAddressStreet"] = values.corAddressStreet; data["corAddressArea"] = values.corAddressArea; data["corAddressCity"] = values.corAddressCity; data["corAddressCountry"] = values.corAddressCountry; data["corAddressPostalCode"] = values.corAddressPostalCode; data = { ...data, // Keep all existing header fields ...earnedIncome, ...unearnedIncome, ...expenditure, ...assets, ...liabilities, totalEarnedCurrent, totalEarnedLast, totalUnearnedCurrent, totalUnearnedLast, totalAssetsCurrent, totalAssetsLast, totalIncomeCurrent, totalIncomeLast, totalExpenditureCurrent, totalExpenditureLast, totalLiquidAssetCurrent, totalLiquidAssetLast, totalNonLiquidAssetCurrent, totalNonLiquidAssetLast, totalLiabilitiesCurrent, totalLiabilitiesLast, }; setClientDetail(data); } else if (isCollectData) { setUserConfirm(false); setIsCollectData(false); } } }, [isCollectData]); useEffect(() => { if (userConfirm) { postClient(); } setUserConfirm(false); }, [clientDetail]); const submitData = () => { setIsCollectData(!isCollectData); setUserConfirm(true); }; const updateIsEdit = () => { setIsEditing(!isEditing); }; function postClient() { setIsUploading(true); const temp = trimDataBeforePost(clientDetail); axios.post(`${apiPath}${POST_CLIENT_PATH}`, temp) .then((response) => { if (response.status === 200) { notifySaveSuccess(); if (isNewRecord) { setIsUploading(false); navigate('/client'); } else { setIsUploading(false); getClientDetail(params.id); setIsEditing(!isEditing); } setIsCollectData(!isCollectData); } }) .catch(error => { if (error.response.status === 422) { const formErrors = {}; formErrors.subDivision = error.response.data.error; setErrors(formErrors); } console.log(error); setIsUploading(false); return false; }); } return ( !onReady ? : Information
Client Code: * Last Name(Surname/Family Name): * First Name(Given Name/Forename): Date of Birth: setDob(newValue)} format="DD/MM/YYYY" onError={(newError) => setDobError(newError)} slotProps={{ field: { clearable: true }, textField: { error: !!errors.dob || dobError, helperText: dobError ? dobErrorMessage : errors.dob, }, }} disabled={!isEditing} /> Email Address: Phone Number: Case Manager: Consultant: option.name || ""} isOptionEqualToValue={(option, value) => option.id === value.id} value={selectedConsultant} onChange={(e, v) => { setSelectedConsultant(v); setValue('consultantId', v); } } renderInput={(params) => ( )} disabled={!isEditing} /> Rs. Address(Room): Rs. Address(Floor): Rs. Address(Block): Rs. Address(Buliding): Rs. Address(Street): Rs. Address(Area): Rs. Address(City): Rs. Address(Country): Rs. Address(Postal Code): Corr. Address(Room): Corr. Address(Floor): Corr. Address(Block): Corr. Address(Buliding): Corr. Address(Street): Corr. Address(Area): Corr. Address(City): Corr. Address(Country): Corr. Address(Postal Code): Remarks: {/* ======================================================================= */} {/* --- START: EARNED INCOME 薪酬收入 (Image 1) --- */} {/* ======================================================================= */} EARNED INCOME 薪酬收入 Current Year 本年度 Last Year 上年度 {/* Salary */} Salary 薪酬 handleEarnedIncomeChange('salaryCurrent', cleanNumberForState(cleanNumberForState(e.target.value)))} /> handleEarnedIncomeChange('salaryLast', cleanNumberForState(cleanNumberForState(e.target.value)))} /> {/* Bonus */} Bonus 花紅 handleEarnedIncomeChange('bonusCurrent', cleanNumberForState(cleanNumberForState(e.target.value)))} /> handleEarnedIncomeChange('bonusLast', cleanNumberForState(e.target.value))} /> {/* Other */} Other 其他 handleEarnedIncomeChange('otherEarnedCurrent', cleanNumberForState(e.target.value))} /> handleEarnedIncomeChange('otherEarnedLast', cleanNumberForState(e.target.value))} /> {/* Total Earned Income */} Total Earned Income 薪酬收入總額
{/* --- END: EARNED INCOME --- */} {/* ======================================================================= */} {/* --- START: UNEARNED INCOME 非薪酬收入 (Image 2 - UPDATED) --- */} {/* ======================================================================= */} UNEARNED INCOME 非薪酬收入 Current Year 本年度 Last Year 上年度 {/* Company Interest */} Company Interest 公司收益 handleUnearnedIncomeChange('companyInterestCurrent', cleanNumberForState(e.target.value))} /> handleUnearnedIncomeChange('companyInterestLast', cleanNumberForState(e.target.value))} /> {/* Dividends */} Dividends 股息 handleUnearnedIncomeChange('dividendsCurrent', cleanNumberForState(e.target.value))} /> handleUnearnedIncomeChange('dividendsLast', cleanNumberForState(e.target.value))} /> {/* Rentals */} Rentals 租金 handleUnearnedIncomeChange('rentalsCurrent', cleanNumberForState(e.target.value))} /> handleUnearnedIncomeChange('rentalsLast', cleanNumberForState(e.target.value))} /> {/* Investment Income */} Investment Income 投資收益 handleUnearnedIncomeChange('investmentIncomeCurrent', cleanNumberForState(e.target.value))} /> handleUnearnedIncomeChange('investmentIncomeLast', cleanNumberForState(e.target.value))} /> {/* Other Unearned */} Other 其他 handleUnearnedIncomeChange('otherUnearnedCurrent', cleanNumberForState(e.target.value))} /> handleUnearnedIncomeChange('otherUnearnedLast', cleanNumberForState(e.target.value))} /> {/* Total Unearned Income */} Total Unearned Income 非薪酬收入總額 {/* Total Income */} Total Income 收入總額
{/* --- END: UNEARNED INCOME --- */} {/* ======================================================================= */} {/* --- START: EXPENDITURE STATEMENT 開支報表 (Image 3) --- */} {/* ======================================================================= */} Expenditure Statement 開支報表 Current Year 本年度 Last Year 上年度 {/* Mortgage */} Mortgage 按揭 handleExpenditureChange('mortgageCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('mortgageLast', cleanNumberForState(e.target.value))} /> {/* Rent */} Rent 租金 handleExpenditureChange('rentCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('rentLast', cleanNumberForState(e.target.value))} /> {/* Schooling for Children */} Schooling for Children 子女教育 handleExpenditureChange('schoolingCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('schoolingLast', cleanNumberForState(e.target.value))} /> {/* Memberships */} Memberships 會籍 handleExpenditureChange('membershipsCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('membershipsLast', cleanNumberForState(e.target.value))} /> {/* Other Expenditure */} Other 其他 handleExpenditureChange('otherExpenditureCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('otherExpenditureLast', cleanNumberForState(e.target.value))} /> {/* Interest */} Interest 利息還款額 handleExpenditureChange('interestCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('interestLast', cleanNumberForState(e.target.value))} /> {/* Principal */} Principal 本金還款額 handleExpenditureChange('principalCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('principalLast', cleanNumberForState(e.target.value))} /> {/* Other */} Other Loan 其他 handleExpenditureChange('otherPrefFinanceCurrent', cleanNumberForState(e.target.value))} /> handleExpenditureChange('otherPrefFinanceLast', cleanNumberForState(e.target.value))} /> {/* Empty Rows (from your image) - you can remove these if not strictly needed for layout */} {/* Total Expenditure */} Total Expenditure 開支總額
{/* --- END: EXPENDITURE STATEMENT --- */} {/* ======================================================================= */} {/* --- START: LIQUID & NON-LIQUID ASSET 流動資產 & 非流動資產 (Image 4) --- */} {/* ======================================================================= */} Liquid Asset 流動資產 Current Year 本年度 Last Year 上年度 {/* Cash and Fixed Deposits */} Cash and Fixed Deposits 現金與定期存款 handleAssetsChange('cashDepositsCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('cashDepositsLast', cleanNumberForState(e.target.value))} /> {/* Investments */} Investments 投資 handleAssetsChange('investmentsCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('investmentsLast', cleanNumberForState(e.target.value))} /> {/* Other Liquid */} Other 其他 handleAssetsChange('otherLiquidCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('otherLiquidLast', cleanNumberForState(e.target.value))} /> {/* Total Liquid Asset */} Total Liquid Asset 流動資產總額
Non-Liquid Asset 非流動資產 Current Year 本年度 Last Year 上年度 {/* Net Business Interest */} Net Business Interest 淨商業利益 handleAssetsChange('netBusinessInterestCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('netBusinessInterestLast', cleanNumberForState(e.target.value))} /> {/* Personal Properties */} Personal Properties 個人財產 handleAssetsChange('personalPropertiesCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('personalPropertiesLast', cleanNumberForState(e.target.value))} /> {/* Real Estate - NEW ROW */} Real Estate 房地產 handleAssetsChange('realEstateCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('realEstateLast', cleanNumberForState(e.target.value))} /> {/* Other Non-Liquid */} Other 其他 handleAssetsChange('otherNonLiquidCurrent', cleanNumberForState(e.target.value))} /> handleAssetsChange('otherNonLiquidLast', cleanNumberForState(e.target.value))} /> {/* Total Non-Liquid Asset */} Total Non-Liquid Asset 非流動資產總額 {/* Total Assets */} Total Assets 資產總額
{/* --- END: LIQUID & NON-LIQUID ASSET --- */} {/* ======================================================================= */} {/* --- START: LIABILITIES 負債 (Image 5) --- */} {/* ======================================================================= */} Liabilities 負債 Current Year 本年度 Last Year 上年度 {/* Personal Loans */} Personal Loans 個人債務 handleLiabilitiesChange('personalLoansCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('personalLoansLast', cleanNumberForState(e.target.value))} /> {/* Mortgages */} Mortgages 按揭 handleLiabilitiesChange('mortgagesCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('mortgagesLast', cleanNumberForState(e.target.value))} /> {/* Margin Account */} Margin Account 保證金賬戶 handleLiabilitiesChange('marginAccountCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('marginAccountLast', cleanNumberForState(e.target.value))} /> {/* Loan Guarantees */} Loan Guarantees 債務擔保 handleLiabilitiesChange('loanGuaranteesCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('loanGuaranteesLast', cleanNumberForState(e.target.value))} /> {/* Banking Facility */} Banking Facility 銀行貸款 handleLiabilitiesChange('bankingFacilityCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('bankingFacilityLast', cleanNumberForState(e.target.value))} /> {/* Sub-header for Existing Premium Financing/Policy loan */} Existing Premium Financing/ Policy loan 現有保費融資/保單貸款 {/* Principal */} Principal 本金還款額 handleLiabilitiesChange('prefFinancePrincipalCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('prefFinancePrincipalLast', cleanNumberForState(e.target.value))} /> {/* Other Liabilities */} Other 其他 handleLiabilitiesChange('prefFinanceOtherCurrent', cleanNumberForState(e.target.value))} /> handleLiabilitiesChange('prefFinanceOtherLast', cleanNumberForState(e.target.value))} /> {/* Total Liabilities */} Total Liabilities 負債總額
{/* --- END: LIABILITIES --- */} {(isEditing) ? : {/* The Save button should only show/be enabled when editing */} {/* ADDED: onClick={handleEditClick} and conditional display/disabled */} }
); }; export default ClientForm;