feat: add account groups, scheduled payments and cash flow calendar
This commit is contained in:
1 parent
a7e0a0fe58
commit
86c6daf695
46 files changed
+2548
-290
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import { useId, useRef, useState } from 'react';
|
||||
import { type Position, money, positionAmount } from './api';
|
||||
import { t as tr } from './i18n';
|
||||
import { iconUrl } from './IconLibrary';
|
||||
export function AccountPicker({
|
||||
name,
|
||||
accounts,
|
||||
value,
|
||||
onChange,
|
||||
excludeId,
|
||||
}: {
|
||||
name: string;
|
||||
accounts: Position[];
|
||||
value: string;
|
||||
onChange: (id: string) => void;
|
||||
excludeId?: string;
|
||||
}) {
|
||||
const dialog = useRef<HTMLDialogElement>(null),
|
||||
title = useId();
|
||||
const [grouped, setGrouped] = useState(true),
|
||||
[query, setQuery] = useState('');
|
||||
const selected = accounts.find((p) => p.id === value);
|
||||
const rows = accounts.filter(
|
||||
(p) =>
|
||||
p.id !== excludeId &&
|
||||
`${p.name} ${p.groupName || ''} ${p.currency}`.toLowerCase().includes(query.toLowerCase()),
|
||||
);
|
||||
const groups = grouped ? [...new Set(rows.map((p) => p.groupName || '未分组'))] : ['全部账户'];
|
||||
return (
|
||||
<div className="account-picker">
|
||||
<input name={name} type="hidden" value={value} />
|
||||
<button
|
||||
className="secondary picker-trigger"
|
||||
type="button"
|
||||
onClick={() => dialog.current?.showModal()}
|
||||
>
|
||||
{selected
|
||||
? `${selected.name} · ${money(positionAmount(selected), selected.currency)}`
|
||||
: tr('选择账户')}
|
||||
</button>
|
||||
<dialog ref={dialog} className="picker-dialog" aria-labelledby={title}>
|
||||
<div className="panel-title">
|
||||
<h2 id={title}>{tr('选择账户')}</h2>
|
||||
<button type="button" onClick={() => dialog.current?.close()} aria-label={tr('关闭')}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
placeholder={tr('搜索账户、分组或币种')}
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<div className="actions">
|
||||
<button type="button" aria-pressed={!grouped} onClick={() => setGrouped(false)}>
|
||||
{tr('全部账户')}
|
||||
</button>
|
||||
<button type="button" aria-pressed={grouped} onClick={() => setGrouped(true)}>
|
||||
{tr('按分组显示')}
|
||||
</button>
|
||||
</div>
|
||||
{groups.map((group) => (
|
||||
<section key={group}>
|
||||
<h3>{tr(group)}</h3>
|
||||
{rows
|
||||
.filter((p) => !grouped || (p.groupName || '未分组') === group)
|
||||
.map((p) => (
|
||||
<button
|
||||
type="button"
|
||||
className="picker-option"
|
||||
key={p.id}
|
||||
onClick={() => {
|
||||
onChange(p.id);
|
||||
dialog.current?.close();
|
||||
}}
|
||||
>
|
||||
{p.iconId && <img src={iconUrl(p.iconId)} alt="" />}
|
||||
<span>
|
||||
{p.name}
|
||||
<small>{money(positionAmount(p), p.currency)}</small>
|
||||
</span>
|
||||
{p.id === value && <span>✓</span>}
|
||||
</button>
|
||||
))}
|
||||
</section>
|
||||
))}
|
||||
{!rows.length && <p>{tr('没有符合条件的账户')}</p>}
|
||||
</dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user