2026-03-17 11:19:26 +08:00
|
|
|
|
import { Icon } from './icons';
|
|
|
|
|
|
|
|
|
|
|
|
export default function RecordModal({
|
|
|
|
|
|
isOpen,
|
|
|
|
|
|
onClose,
|
|
|
|
|
|
onSubmit,
|
|
|
|
|
|
onDelete,
|
|
|
|
|
|
formData,
|
|
|
|
|
|
setFormData,
|
|
|
|
|
|
formError,
|
|
|
|
|
|
submitting,
|
|
|
|
|
|
deleting,
|
|
|
|
|
|
editingRecord,
|
|
|
|
|
|
categories,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
if (!isOpen) return null;
|
|
|
|
|
|
|
|
|
|
|
|
const handleTypeChange = (type) => {
|
|
|
|
|
|
setFormData({ ...formData, type, category: '' });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const isDisabled = submitting || deleting;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="modal-overlay modal-overlay--fullscreen" onClick={onClose}>
|
|
|
|
|
|
<div className="modal modal--fullscreen" onClick={(e) => e.stopPropagation()}>
|
2026-03-18 16:33:29 +08:00
|
|
|
|
<div className="modal-header" style={{minHeight: 56, height: 56, padding: 'calc(var(--safe-top) + 8px) 16px 8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', boxSizing: 'border-box'}}>
|
|
|
|
|
|
<h2 style={{margin: 0, fontSize: '1.1rem'}}>{editingRecord ? '编辑记录' : '新增记录'}</h2>
|
2026-03-17 11:19:26 +08:00
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="icon-button icon-button--ghost"
|
|
|
|
|
|
aria-label="关闭弹窗"
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon name="close" size={18} />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{formError && <div className="error-msg">{formError}</div>}
|
|
|
|
|
|
<form onSubmit={onSubmit}>
|
|
|
|
|
|
<div className="type-switch">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`type-btn expense ${formData.type === 'expense' ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => handleTypeChange('expense')}
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
>
|
|
|
|
|
|
支出
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`type-btn income ${formData.type === 'income' ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => handleTypeChange('income')}
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
>
|
|
|
|
|
|
收入
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="form-group">
|
|
|
|
|
|
<label htmlFor="record-amount">金额</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
id="record-amount"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
className="amount-input"
|
|
|
|
|
|
value={formData.amount}
|
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, amount: e.target.value })}
|
|
|
|
|
|
placeholder="0.00"
|
|
|
|
|
|
step="0.01"
|
|
|
|
|
|
required
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="form-group">
|
|
|
|
|
|
<label>分类</label>
|
|
|
|
|
|
<div className="category-grid">
|
|
|
|
|
|
{categories[formData.type].map((cat) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={cat.name}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`category-btn ${formData.category === cat.name ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => setFormData({ ...formData, category: cat.name })}
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className="icon">{cat.icon}</span>
|
|
|
|
|
|
<span>{cat.name}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="form-group">
|
|
|
|
|
|
<label>日期</label>
|
|
|
|
|
|
<div className="datetime-picker">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
value={formData.date}
|
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, date: e.target.value })}
|
|
|
|
|
|
required
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="time"
|
|
|
|
|
|
value={formData.time || '00:00'}
|
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, time: e.target.value })}
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="form-group">
|
|
|
|
|
|
<label htmlFor="record-note">备注(可选)</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
id="record-note"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={formData.note}
|
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, note: e.target.value })}
|
|
|
|
|
|
placeholder="添加备注..."
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="modal-actions">
|
|
|
|
|
|
<button type="submit" className="btn btn-primary" disabled={isDisabled}>
|
|
|
|
|
|
{submitting ? (
|
|
|
|
|
|
<span className="button-content">
|
|
|
|
|
|
<Icon name="spinner" size={16} className="is-spinning" />
|
|
|
|
|
|
保存中...
|
|
|
|
|
|
</span>
|
|
|
|
|
|
) : '保存'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{editingRecord && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="btn btn-danger"
|
|
|
|
|
|
onClick={onDelete}
|
|
|
|
|
|
disabled={isDisabled}
|
|
|
|
|
|
>
|
|
|
|
|
|
{deleting ? '删除中...' : '删除'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|