diff --git a/src/components/transaction/TransactionForm/TransactionForm.tsx b/src/components/transaction/TransactionForm/TransactionForm.tsx index 13cac8c..c664ebf 100644 --- a/src/components/transaction/TransactionForm/TransactionForm.tsx +++ b/src/components/transaction/TransactionForm/TransactionForm.tsx @@ -69,6 +69,16 @@ export const TransactionForm: React.FC = ({ // Current step (1, 2, or 3) const [currentStep, setCurrentStep] = useState(1); + + // Helpers + const toLocalISOString = (date: Date) => { + const pad = (n: number) => n.toString().padStart(2, '0'); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`; + }; + + const selectedAccount = accounts.find((a) => a.id === formData.accountId); + const selectedCategory = categories.find((c) => c.id === formData.categoryId); + // Form State const [formData, setFormData] = useState({ amount: 0, @@ -77,18 +87,18 @@ export const TransactionForm: React.FC = ({ categoryId: 0, accountId: 0, // Initialize with current local time for datetime-local input + // If initialData is provided (RFC3339), convert to local time string transactionDate: initialData?.transactionDate - ? (initialData.transactionDate.includes('T') ? initialData.transactionDate.substring(0, 16) : initialData.transactionDate) - : (() => { - const now = new Date(); - const pad = (n: number) => n.toString().padStart(2, '0'); - return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`; - })(), + ? toLocalISOString(new Date(initialData.transactionDate)) + : toLocalISOString(new Date()), note: '', tagIds: [], ...initialData, }); + // Local state for Amount input to allow typing decimals (e.g. "12.") + const [amountStr, setAmountStr] = useState(initialData?.amount?.toString() || ''); + // Data State const [accounts, setAccounts] = useState([]); const [categories, setCategories] = useState([]); @@ -127,124 +137,26 @@ export const TransactionForm: React.FC = ({ }; const handleAmountChange = (e: React.ChangeEvent) => { - // Basic number input handling const val = e.target.value; - // Allow empty string or ending with decimal - if (val === '' || /^\d*\.?\d*$/.test(val)) { - // We might store it as string locally if we want perfect input, but TransactionFormInput expects number. - // For simplicity, we parse float immediately but this prevents typing "1." comfortably. - // Let's rely on type="number" behavior of input if possible or assume user types clean numbers. - // Or better: update state properly. - // The original broken code used `value={formData.amount || ''}` and `inputMode="decimal"`. - // Let's do simple float parsing. - setFormData((prev) => ({ ...prev, amount: parseFloat(val) || 0 })); - if (errors.amount) setErrors((prev) => ({ ...prev, amount: '' })); - } - }; - const handleCategoryChange = (categoryId: number | undefined) => { - setFormData((prev) => ({ ...prev, categoryId: categoryId || 0 })); - if (errors.categoryId) setErrors((prev) => ({ ...prev, categoryId: '' })); - }; + // Update display value immediately + setAmountStr(val); - const handleAccountChange = (accountId: number) => { - setFormData((prev) => ({ ...prev, accountId })); - if (errors.accountId) setErrors((prev) => ({ ...prev, accountId: '' })); - }; - - const handleDateChange = (e: React.ChangeEvent) => { - setFormData((prev) => ({ ...prev, transactionDate: e.target.value })); - }; - - const handleNoteChange = (e: React.ChangeEvent) => { - setFormData((prev) => ({ ...prev, note: e.target.value })); - }; - - const handleTagsChange = (tagIds: number[]) => { - setFormData((prev) => ({ ...prev, tagIds })); - }; - - // Navigation - const handleNextStep = useCallback(() => { - if (currentStep === 1) { - // Validate Step 1 - if (!formData.amount || formData.amount <= 0) { - setErrors(prev => ({ ...prev, amount: '请输入有效金额' })); - return; - } - - // Smart Skip - if (smartSkipConfirmedSteps && formData.categoryId && formData.accountId) { - setCurrentStep(3); - } else { - setCurrentStep(2); - } - } else if (currentStep === 2) { - // Validate Step 2 - let isValid = true; - const newErrors = { ...errors }; - if (!formData.categoryId) { newErrors.categoryId = '请选择分类'; isValid = false; } - if (!formData.accountId) { newErrors.accountId = '请选择账户'; isValid = false; } - setErrors(newErrors); - - if (isValid) setCurrentStep(3); - } - }, [currentStep, formData, smartSkipConfirmedSteps, errors]); - - const handlePrevStep = () => { - if (currentStep > 1) { - setCurrentStep(currentStep - 1); - } - }; - - const handleSubmit = useCallback(() => { - if (!formData.amount) return; - if (!formData.categoryId) return; - if (!formData.accountId) return; - - // Convert local datetime-local string to proper ISO string for backend - // datetime-local value format: YYYY-MM-DDTHH:mm - // new Date(val).toISOString() will give UTC time which backend accepts - let submitDate = formData.transactionDate; - try { - const dateObj = new Date(formData.transactionDate); - if (!isNaN(dateObj.getTime())) { - submitDate = dateObj.toISOString(); - } else { - // Fallback for date-only strings if any legacy data - submitDate = new Date(formData.transactionDate + 'T00:00:00').toISOString(); - } - } catch (e) { - console.warn('Date conversion error', e); + // Parse and update form data if valid number + // Allow empty string to just clear form data + if (val === '') { + setFormData((prev) => ({ ...prev, amount: 0 })); + return; } - const payload = { - ...formData, - transactionDate: submitDate - }; - - onSubmit(payload); - }, [formData, onSubmit]); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - if (currentStep < 3) { - handleNextStep(); - } else { - handleSubmit(); - } + if (/^\d*\.?\d*$/.test(val)) { + const parsed = parseFloat(val); + if (!isNaN(parsed)) { + setFormData((prev) => ({ ...prev, amount: parsed })); + if (errors.amount) setErrors((prev) => ({ ...prev, amount: '' })); } - }, - [currentStep, handleNextStep, handleSubmit] - ); - - // Helpers - const selectedAccount = accounts.find((a) => a.id === formData.accountId); - const selectedCategory = categories.find((c) => c.id === formData.categoryId); - - // Render Functions + } + }; const renderStepIndicator = () => (
{[1, 2, 3].map((step) => ( @@ -298,7 +210,7 @@ export const TransactionForm: React.FC = ({ type="number" // Changed to number for simpler handling inputMode="decimal" className={`transaction-form__amount-input ${errors.amount ? 'transaction-form__amount-input--error' : ''}`} - value={formData.amount || ''} + value={amountStr} onChange={handleAmountChange} onKeyDown={handleKeyDown} placeholder="0.00" diff --git a/src/components/transaction/TransactionItem/TransactionItem.tsx b/src/components/transaction/TransactionItem/TransactionItem.tsx index e4e8506..885685a 100644 --- a/src/components/transaction/TransactionItem/TransactionItem.tsx +++ b/src/components/transaction/TransactionItem/TransactionItem.tsx @@ -68,11 +68,8 @@ const TransactionItem = React.memo(({ } }; - // 格式化时间 - const formatTime = (dateString: string, timeString?: string): string => { - if (timeString) { - return timeString.slice(0, 5); - } + // 格式化时间 - Now always derive from transactionDate (DATETIME) + const formatTime = (dateString: string): string => { const date = new Date(dateString); return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); }; @@ -148,7 +145,7 @@ const TransactionItem = React.memo(({ {showDate && (
{formatDate(transaction.transactionDate)} - {transaction.transactionTime && {formatTime(transaction.transactionDate, transaction.transactionTime)}} + {formatTime(transaction.transactionDate)}
)}
@@ -232,11 +229,9 @@ const TransactionItem = React.memo(({ {formatDate(transaction.transactionDate)} - {transaction.transactionTime && ( - - {formatTime(transaction.transactionDate, transaction.transactionTime)} - - )} + + {formatTime(transaction.transactionDate)} + )} diff --git a/src/components/transaction/TransactionReceiptModal/TransactionReceiptModal.tsx b/src/components/transaction/TransactionReceiptModal/TransactionReceiptModal.tsx index b4bc432..3994cb3 100644 --- a/src/components/transaction/TransactionReceiptModal/TransactionReceiptModal.tsx +++ b/src/components/transaction/TransactionReceiptModal/TransactionReceiptModal.tsx @@ -37,9 +37,7 @@ export const TransactionReceiptModal: React.FC = ( day: 'numeric', weekday: 'long', }); - const formattedTime = transaction.transactionTime - ? transaction.transactionTime.slice(0, 5) - : dateObj.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); + const formattedTime = dateObj.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); return (
diff --git a/src/types/index.ts b/src/types/index.ts index 929108a..e11b9a4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -112,21 +112,21 @@ export interface Transaction { // New fields for accounting-feature-upgrade ledgerId?: number; ledger?: Ledger; - transactionTime?: string; - // Reimbursement related fields - reimbursementStatus: ReimbursementStatus; - reimbursementAmount?: number; - reimbursementIncomeId?: number; - // Refund related fields - refundStatus: RefundStatus; - refundAmount?: number; - refundIncomeId?: number; - // Association to original transaction (for refund/reimbursement income records) - originalTransactionId?: number; - originalTransaction?: Transaction; - incomeType?: IncomeType; - // Images - images?: TransactionImage[]; + /** @deprecated Time is now stored in transactionDate (DATETIME). Kept for backward compatibility. */\r\n transactionTime ?: string; +// Reimbursement related fields +reimbursementStatus: ReimbursementStatus; +reimbursementAmount ?: number; +reimbursementIncomeId ?: number; +// Refund related fields +refundStatus: RefundStatus; +refundAmount ?: number; +refundIncomeId ?: number; +// Association to original transaction (for refund/reimbursement income records) +originalTransactionId ?: number; +originalTransaction ?: Transaction; +incomeType ?: IncomeType; +// Images +images ?: TransactionImage[]; } // Account Interface