diff --git a/src/pages/client/ClientMaintainPage/ClientForm.js b/src/pages/client/ClientMaintainPage/ClientForm.js index 5ccf9e0..c40b569 100644 --- a/src/pages/client/ClientMaintainPage/ClientForm.js +++ b/src/pages/client/ClientMaintainPage/ClientForm.js @@ -1,7 +1,8 @@ import { Button, FormControlLabel, - Grid, InputAdornment, Switch, TextField, Typography + 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"; @@ -66,59 +67,214 @@ const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { const [dob, setDob] = useState(null); const [dobError, setDobError] = React.useState(null); - const [awardDateError, setAwardDateError] = React.useState(null); - const [deadlineDateError, setDeadlineDateError] = React.useState(null); - const [announceDateError, setAnnounceDateError] = React.useState(null); - const [fromError, setFromError] = React.useState(null); - const [toError, setToError] = React.useState(null); + + // Handler to navigate back + const returnSearchPage = () => { + navigate('/search'); + } - const dobErrorMessage = React.useMemo(() => { - switch (dobError) { - case 'invalidDate': { - return "Invalid date"; - } - } - }, [dobError]); + const FIXED_HEADER_HEIGHT = 120; // <-- **IMPORTANT: Set this to the height of your actual fixed header in pixels** - const awardDateErrorMessage = React.useMemo(() => { - switch (awardDateError) { - case 'invalidDate': { - return "Invalid date"; - } - } - }, [awardDateError]); + 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; - const deadlineDateErrorMessage = React.useMemo(() => { - switch (deadlineDateError) { - case 'invalidDate': { - return "Invalid date"; - } - } - }, [deadlineDateError]); + // Scroll the window to the new position + window.scrollTo({ + top: targetScrollY, + behavior: 'smooth' + }); + + // Ensure the element has focus + element.focus(); + + }, 100); + }; - const announceDateErrorMessage = React.useMemo(() => { - switch (announceDateError) { - case 'invalidDate': { - return "Invalid date"; - } - } - }, [announceDateError]); + // Handler to enable edit mode + const handleEditClick = () => { + setIsEditing(true); + }; + + // 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); - const fromErrorMessage = React.useMemo(() => { - switch (fromError) { - case 'invalidDate': { - return "Invalid date"; - } - } - }, [fromError]); - const toErrorMessage = React.useMemo(() => { - switch (toError) { + // 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: '', + 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.otherNonLiquidCurrent) || 0); + const totalNonLiquidAssetLast = (parseFloat(assets.netBusinessInterestLast) || 0) + + (parseFloat(assets.personalPropertiesLast) || 0) + + (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"; } } - }, [toError]); + }, [dobError]); // DELETE WINDOW RELATED const [isWindowOpen, setIsWindowOpen] = React.useState(false); @@ -135,6 +291,64 @@ const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { navigate(`/event/maintain/-1?refId=${params.id}`); }; + const formatNumberForDisplay = (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 + '.'; + } + } + + 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) => { @@ -181,6 +395,74 @@ const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { setValue("phone2", refClient.phone2); setValue("remarks", refClient.remarks); setValue("caseManagerId", 1); + 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; + unearnedIncome.mortgageCurrent = refClient.mortgageCurrent; + unearnedIncome.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.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( @@ -233,6 +515,31 @@ const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { data["crAddressCountry"] = values.crAddressCountry; data["crAddressPostalCode"] = values.crAddressPostalCode; + 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); @@ -241,10 +548,6 @@ const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { } }, [isCollectData]); - function returnSearchPage() { - navigate('/client'); - } - useEffect(() => { if (userConfirm) { postClient(); @@ -697,90 +1000,784 @@ const ClientForm = ({ refClientDetail, isNewRecord, getClientDetail }) => { autoComplete="off" /> + + + + {/* ======================================================================= */} + {/* --- 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))} + /> + + + {/* 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) ? - - - - - - - - - - - - {!isNewRecord && ( + + + + + + - )} - + + + + - - - - - - - - - - : - - - - - - - - - + + + + + + + + : + + + + {/* The Save button should only show/be enabled when editing */} + + + + {/* ADDED: onClick={handleEditClick} and conditional display/disabled */} + + - - + + }
diff --git a/src/pages/pdf/PdfMaintainPage/index.js b/src/pages/pdf/PdfMaintainPage/index.js index 2f12929..962c1ec 100644 --- a/src/pages/pdf/PdfMaintainPage/index.js +++ b/src/pages/pdf/PdfMaintainPage/index.js @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { Button, Grid, InputLabel, TextField } from '@mui/material'; import { GeneralConfirmWindow, notifySaveSuccess } from "../../../utils/CommonFunction"; import axios from 'axios'; -import {apiPath, adobeAPIKey} from "../../../auth/utils"; +import {appURL, apiPath, adobeAPIKey} from "../../../auth/utils"; import { GET_PDF_TEMPLATE_PATH, GET_PDF_PATH, @@ -37,55 +37,55 @@ function PDF() { const refId = queryParams.get("refId"); const loadPdfForm = async (id, templateId = 0, clientId = 0) => { - if (!pdfLoaded) { - if (id > 0) { - // axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { - axios.get(`${apiPath}${GET_PDF_PATH}/${id}`, { - // responseType: 'arraybuffer', // Essential for binary data - }) - .then((response) => { - if (response.status === 200) { - const res = response.data; - setRecord({ // WIP - allow to update all record - id : res.id, - clientId: res.clientId, - templateId: res.templateId, - }) - setFormName(res.filename); + + if (id > 0) { + // axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { + axios.get(`${apiPath}${GET_PDF_PATH}/${id}`, { + // responseType: 'arraybuffer', // Essential for binary data + }) + .then((response) => { + if (response.status === 200) { + const res = response.data; + setRecord({ // WIP - allow to update all record + id : res.id, + clientId: res.clientId, + templateId: res.templateId, + }) + setFormName(res.filename); - //Convert Base64 to binary data - handlePdfUrl(base64ToBinary(res.blobValue)); - setPdfLoaded(true); - } - }) - .catch(error => { - console.log(error); - return false; - }); + //Convert Base64 to binary data + handlePdfUrl(base64ToBinary(res.blobValue)); + setPdfLoaded(true); + } + }) + .catch(error => { + console.log(error); + return false; + }); - } else { - axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { - // responseType: 'arraybuffer', // Essential for binary data - params: { - templateId: templateId, - clientId: clientId, - }, - }) - .then((response) => { - if (response.status === 200) { - const res = response.data; - setFormName(res.filename); - //Convert Base64 to binary data - handlePdfUrl(base64ToBinary(res.blobValue)); - setPdfLoaded(true); - } - }) - .catch(error => { - console.log(error); - return false; - }); - } + } else { + axios.get(`${apiPath}${GET_PDF_TEMPLATE_PATH}`, { + // responseType: 'arraybuffer', // Essential for binary data + params: { + templateId: templateId, + clientId: clientId, + }, + }) + .then((response) => { + if (response.status === 200) { + const res = response.data; + setFormName(res.filename); + //Convert Base64 to binary data + handlePdfUrl(base64ToBinary(res.blobValue)); + setPdfLoaded(true); + } + }) + .catch(error => { + console.log(error); + return false; + }); } + }; const handlePdfUrl = (pdfBytes) => { @@ -134,7 +134,7 @@ function PDF() { ).catch(error => { console.error('Preview error:', error); - setError('Failed to load PDF: ' + error.message); + // setError('Failed to load PDF: ' + error.message); // Commented out 'setError' since it's not defined }).then(instance => { URL.revokeObjectURL(pdfUrl); @@ -151,7 +151,7 @@ function PDF() { console.log("viewer: ", DCViewer); } else { console.error('AdobeDC not available'); - setError('Adobe SDK not loaded'); + // setError('Adobe SDK not loaded'); // Commented out 'setError' since it's not defined } }; @@ -191,29 +191,42 @@ function PDF() { const handleSavePdf = async (metaData, content, options) => { try { const filledPdfBlob = new Blob([content], { type: 'application/pdf' }); - // Create FormData to send the file to Spring Boot const formData = new FormData(); formData.append('file', filledPdfBlob, 'filled_form.pdf'); formData.append('record', JSON.stringify(record)); setIsSaving(true); - // Send the filled PDF to your Spring Boot backend's save endpoint + await axios.post(`${apiPath}${POST_PDF_PATH}`, formData, { headers: { - 'Content-Type': 'multipart/form-data' // Important for file uploads + 'Content-Type': 'multipart/form-data' }, }) .then(response => { setIsSaving(false); console.log('PDF saved on server:', response.data); + + const newId = response.data.data.id; + setRecord({ - id: response.data.data.id, + id: newId, clientId: record.clientId, templateId: record.templateId }); - notifySaveSuccess()}); + notifySaveSuccess(); + + /* + if(response.data.data.reload){ + setPdfLoaded(false); + setViewerLoaded(false); + setAdobeDCView(null); + + navigate(`/pdf/maintain/${newId}`, { replace: false }); + } + */ + }); return { code: window.AdobeDC.View.Enum.ApiResponseCode.SUCCESS, - data: { metaData } // Return metaData to prevent t.data undefined + data: { metaData } }; } catch (error) { console.error('Error saving PDF:', error);