/** * AllocationRuleForm Component * Form for creating and editing income allocation rules * * Requirements: 5.3.2, 5.3.5 */ import React, { useState, useEffect } from 'react'; import type { AllocationRule, Account, PiggyBank } from '../../../types'; import type { AllocationRuleFormInput, AllocationTargetInput, } from '../../../services/allocationRuleService'; import { getTargetTypeLabel } from '../../../services/allocationRuleService'; import './AllocationRuleForm.css'; interface AllocationRuleFormProps { allocationRule?: AllocationRule; accounts: Account[]; piggyBanks: PiggyBank[]; onSubmit: (data: AllocationRuleFormInput) => void; onCancel: () => void; isLoading?: boolean; } export const AllocationRuleForm: React.FC = ({ allocationRule, accounts, piggyBanks, onSubmit, onCancel, isLoading = false, }) => { const [formData, setFormData] = useState({ name: '', triggerType: 'income', sourceAccountId: undefined, isActive: true, targets: [], }); const [errors, setErrors] = useState>({}); // Initialize form with allocation rule data if editing useEffect(() => { if (allocationRule) { setFormData({ name: allocationRule.name, triggerType: allocationRule.triggerType as 'income' | 'manual', sourceAccountId: allocationRule.sourceAccountId, isActive: allocationRule.isActive, targets: allocationRule.targets.map((target) => ({ targetType: target.targetType, targetId: target.targetId, percentage: target.percentage, fixedAmount: target.fixedAmount, })), }); } }, [allocationRule]); const handleChange = (e: React.ChangeEvent) => { const { name, value, type } = e.target; const checked = (e.target as HTMLInputElement).checked; setFormData((prev) => ({ ...prev, [name]: type === 'checkbox' ? checked : value, })); // Clear error for this field if (errors[name]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[name]; return newErrors; }); } }; const handleAddTarget = () => { setFormData((prev) => ({ ...prev, targets: [ ...prev.targets, { targetType: 'account', targetId: accounts.length > 0 ? accounts[0].id : 0, percentage: 0, }, ], })); }; const handleRemoveTarget = (index: number) => { setFormData((prev) => ({ ...prev, targets: prev.targets.filter((_, i) => i !== index), })); }; const handleTargetChange = ( index: number, field: keyof AllocationTargetInput, value: string | number ) => { setFormData((prev) => { const newTargets = [...prev.targets]; const target = { ...newTargets[index] }; if (field === 'targetType') { target.targetType = value as 'account' | 'piggy_bank'; // Reset target ID when type changes if (value === 'account') { target.targetId = accounts.length > 0 ? accounts[0].id : 0; } else { target.targetId = piggyBanks.length > 0 ? piggyBanks[0].id : 0; } } else if (field === 'targetId') { target.targetId = Number(value); } else if (field === 'percentage') { target.percentage = value === '' ? undefined : Number(value); target.fixedAmount = undefined; // Clear fixed amount when percentage is set } else if (field === 'fixedAmount') { target.fixedAmount = value === '' ? undefined : Number(value); target.percentage = undefined; // Clear percentage when fixed amount is set } newTargets[index] = target; return { ...prev, targets: newTargets }; }); // Clear target errors if (errors[`target_${index}`]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[`target_${index}`]; return newErrors; }); } }; const handleAllocationTypeChange = (index: number, type: 'percentage' | 'fixed') => { setFormData((prev) => { const newTargets = [...prev.targets]; const target = { ...newTargets[index] }; if (type === 'percentage') { target.percentage = 0; target.fixedAmount = undefined; } else { target.fixedAmount = 0; target.percentage = undefined; } newTargets[index] = target; return { ...prev, targets: newTargets }; }); }; const validate = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = '请输入规则名称'; } if (formData.targets.length === 0) { newErrors.targets = '至少需要添加一个分配目标'; } let totalPercentage = 0; formData.targets.forEach((target, index) => { if (!target.targetId) { newErrors[`target_${index}`] = '请选择目标'; } if (target.percentage === undefined && target.fixedAmount === undefined) { newErrors[`target_${index}`] = '请设置分配比例或固定金额'; } if (target.percentage !== undefined) { if (target.percentage < 0 || target.percentage > 100) { newErrors[`target_${index}`] = '分配比例必须在0-100之间'; } totalPercentage += target.percentage; } if (target.fixedAmount !== undefined && target.fixedAmount <= 0) { newErrors[`target_${index}`] = '固定金额必须大于0'; } }); if (totalPercentage > 100) { newErrors.targets = '总分配比例不能超过100%'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (validate()) { onSubmit(formData); } }; const getTotalPercentage = (): number => { return formData.targets.reduce((sum, target) => { return sum + (target.percentage || 0); }, 0); }; const totalPercentage = getTotalPercentage(); return (

{allocationRule ? '编辑分配规则' : '创建分配规则'}

{/* Rule Name */}
{errors.name && {errors.name}}
{/* Trigger Type */}
{/* Source Account - only show when trigger type is income */} {formData.triggerType === 'income' && (
选择特定账户后,只有该账户收到收入时才会触发分配建议
)} {/* Is Active */}
{/* Allocation Targets */}

分配目标 *

{errors.targets && ( {errors.targets} )} {formData.targets.length === 0 ? (

暂无分配目标,点击"添加目标"按钮开始配置

) : (
{formData.targets.map((target, index) => (
目标 {index + 1}
{/* Target Type */}
{/* Target Selection */}
{/* Allocation Type Toggle */}
{/* Percentage or Fixed Amount Input */} {target.percentage !== undefined ? (
handleTargetChange(index, 'percentage', e.target.value)} className="allocation-rule-form__input" placeholder="0" min="0" max="100" step="0.01" disabled={isLoading} />
) : (
handleTargetChange(index, 'fixedAmount', e.target.value)} className="allocation-rule-form__input" placeholder="0.00" min="0" step="0.01" disabled={isLoading} />
)} {errors[`target_${index}`] && ( {errors[`target_${index}`]} )}
))}
)} {/* Total Percentage Display */} {totalPercentage > 0 && (
总分配比例: 100 ? 'allocation-rule-form__total-value--error' : ''}`} > {totalPercentage.toFixed(2)}% {totalPercentage < 100 && ( (剩余 {(100 - totalPercentage).toFixed(2)}% 将保留在原账户) )}
)}
); }; export default AllocationRuleForm;