Files
real-time-fund/app/components/AddHistoryModal.jsx

229 lines
7.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { CloseIcon } from './Icons';
import { fetchSmartFundNetValue } from '../api/fund';
import { DatePicker } from './Common';
import {
Dialog,
DialogContent,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
export default function AddHistoryModal({ fund, onClose, onConfirm }) {
const [type, setType] = useState('');
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
const [amount, setAmount] = useState('');
const [share, setShare] = useState('');
const [netValue, setNetValue] = useState(null);
const [netValueDate, setNetValueDate] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
if (!fund || !date) return;
const getNetValue = async () => {
setLoading(true);
setError(null);
setNetValue(null);
setNetValueDate(null);
try {
const result = await fetchSmartFundNetValue(fund.code, date);
if (result && result.value) {
setNetValue(result.value);
setNetValueDate(result.date);
} else {
setError('未找到该日期的净值数据');
}
} catch (err) {
console.error(err);
setError('获取净值失败');
} finally {
setLoading(false);
}
};
const timer = setTimeout(getNetValue, 500);
return () => clearTimeout(timer);
}, [fund, date]);
// Recalculate share when netValue变化或金额变化
useEffect(() => {
if (netValue && amount) {
setShare((parseFloat(amount) / netValue).toFixed(2));
}
}, [netValue, amount]);
const handleAmountChange = (e) => {
const val = e.target.value;
setAmount(val);
if (netValue && val) {
setShare((parseFloat(val) / netValue).toFixed(2));
} else if (!val) {
setShare('');
}
};
const handleSubmit = () => {
if (!type || !date || !netValue || !amount || !share) return;
onConfirm({
fundCode: fund.code,
type,
date: netValueDate, // Use the date from net value to be precise
amount: parseFloat(amount),
share: parseFloat(share),
price: netValue,
timestamp: new Date(netValueDate).getTime()
});
onClose();
};
const handleOpenChange = (open) => {
if (!open) {
onClose?.();
}
};
const handleCloseClick = (event) => {
event.stopPropagation();
onClose?.();
};
return (
<Dialog open onOpenChange={handleOpenChange}>
<DialogContent
showCloseButton={false}
className="glass card modal"
overlayClassName="modal-overlay"
overlayStyle={{ zIndex: 9998 }}
style={{ maxWidth: '420px', zIndex: 9999, width: '90vw' }}
>
<DialogTitle className="sr-only">添加历史记录</DialogTitle>
<div className="title" style={{ marginBottom: 20, justifyContent: 'space-between' }}>
<span>添加历史记录</span>
<button
className="icon-button"
onClick={handleCloseClick}
style={{ border: 'none', background: 'transparent' }}
>
<CloseIcon width="20" height="20" />
</button>
</div>
<div style={{ marginBottom: 20 }}>
<div style={{ fontSize: '14px', fontWeight: 600 }}>{fund?.name}</div>
<div className="muted" style={{ fontSize: '12px' }}>{fund?.code}</div>
</div>
<div className="form-group" style={{ marginBottom: 16 }}>
<label className="label">
交易类型 <span style={{ color: 'var(--danger)' }}>*</span>
</label>
<div style={{ display: 'flex', gap: 8 }}>
<label
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 6,
padding: '6px 10px',
borderRadius: 8,
border: type === 'buy' ? '1px solid var(--primary)' : '1px solid var(--border)',
background: type === 'buy' ? 'rgba(34,211,238,0.08)' : 'transparent',
cursor: 'pointer',
fontSize: '13px'
}}
>
<input
type="radio"
name="history-type"
value="buy"
checked={type === 'buy'}
onChange={(e) => setType(e.target.value)}
style={{ accentColor: 'var(--primary)' }}
/>
<span>买入</span>
</label>
<label
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 6,
padding: '6px 10px',
borderRadius: 8,
border: type === 'sell' ? '1px solid var(--danger)' : '1px solid var(--border)',
background: type === 'sell' ? 'rgba(248,113,113,0.08)' : 'transparent',
cursor: 'pointer',
fontSize: '13px'
}}
>
<input
type="radio"
name="history-type"
value="sell"
checked={type === 'sell'}
onChange={(e) => setType(e.target.value)}
style={{ accentColor: 'var(--danger)' }}
/>
<span>卖出</span>
</label>
</div>
</div>
<div className="form-group" style={{ marginBottom: 16 }}>
<label className="label">
交易日期 <span style={{ color: 'var(--danger)' }}>*</span>
</label>
<DatePicker value={date} onChange={setDate} />
{loading && <div className="muted" style={{ fontSize: '12px', marginTop: 4 }}>正在获取净值...</div>}
{error && <div style={{ fontSize: '12px', color: 'var(--danger)', marginTop: 4 }}>{error}</div>}
{netValue && !loading && (
<div style={{ fontSize: '12px', color: 'var(--success)', marginTop: 4 }}>
参考净值: {netValue} ({netValueDate})
</div>
)}
</div>
<div className="form-group" style={{ marginBottom: 24 }}>
<label className="label">
金额 (¥) <span style={{ color: 'var(--danger)' }}>*</span>
</label>
<input
type="number"
inputMode="decimal"
className="input"
value={amount}
onChange={handleAmountChange}
placeholder="0.00"
step="0.01"
disabled={!netValue}
/>
</div>
<div className="muted" style={{ fontSize: '11px', lineHeight: 1.5, marginBottom: 16, paddingTop: 12, borderTop: '1px solid rgba(255,255,255,0.08)' }}>
*此处补录的买入/卖出仅作记录展示不会改变当前持仓金额与份额实际持仓请在持仓设置中维护
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
type="button"
variant="default"
size="lg"
onClick={handleSubmit}
disabled={!type || !date || !netValue || !amount || !share || loading}
>
确认添加
</Button>
</div>
</DialogContent>
</Dialog>
);
}