- | {money(h.before, h.currency)} |
- {money(h.after, h.currency)} |
- {money(h.delta, h.currency)} |
+
+ {money(
+ h.kind === 'debt' && h.side === 'liability' && Number(h.before) !== 0
+ ? '-' + h.before
+ : h.before,
+ h.currency,
+ )}
+ |
+
+ {money(
+ h.kind === 'debt' && h.side === 'liability' && Number(h.after) !== 0
+ ? '-' + h.after
+ : h.after,
+ h.currency,
+ )}
+ |
+
+ {money(
+ h.kind === 'debt' && h.side === 'liability'
+ ? h.delta.startsWith('-')
+ ? h.delta.slice(1)
+ : Number(h.delta) !== 0
+ ? '-' + h.delta
+ : h.delta
+ : h.delta,
+ h.currency,
+ )}
+ |
{h.notes || '—'} |
{correct && (
diff --git a/apps/web/src/Calendar.tsx b/apps/web/src/Calendar.tsx
new file mode 100644
index 0000000..e2e05a8
--- /dev/null
+++ b/apps/web/src/Calendar.tsx
@@ -0,0 +1,168 @@
+import { useEffect, useState } from 'react';
+import { api, money, today } from './api';
+import { t as tr } from './i18n';
+type Day = { date: string; income: string; expense: string; complete: boolean };
+type Entry = {
+ id: string;
+ name: string;
+ direction: string;
+ amount: string;
+ currency: string;
+ converted: string | null;
+ notes: string;
+};
+export function Calendar({
+ revealed,
+ refreshVersion,
+}: {
+ revealed: boolean;
+ refreshVersion: number;
+}) {
+ const [month, setMonth] = useState(today().slice(0, 7)),
+ [selected, setSelected] = useState(today());
+ const [days, setDays] = useState([]),
+ [rows, setRows] = useState([]),
+ [base, setBase] = useState('CNY'),
+ [error, setError] = useState(''),
+ [loading, setLoading] = useState(false);
+ useEffect(() => {
+ let active = true;
+ setDays([]);
+ setError('');
+ void api<{ items: Day[]; baseCurrency: string }>('/calendar?month=' + month)
+ .then((v) => {
+ if (active) {
+ setDays(v.items);
+ setBase(v.baseCurrency);
+ }
+ })
+ .catch((e) => {
+ if (active) setError(e.message);
+ });
+ return () => {
+ active = false;
+ };
+ }, [month, revealed, refreshVersion]);
+ useEffect(() => {
+ let active = true;
+ setRows([]);
+ setLoading(true);
+ void api<{ items: Entry[] }>('/calendar/day?date=' + selected)
+ .then((v) => {
+ if (active) setRows(v.items);
+ })
+ .catch((e) => {
+ if (active) setError(e.message);
+ })
+ .finally(() => {
+ if (active) setLoading(false);
+ });
+ return () => {
+ active = false;
+ };
+ }, [selected, revealed, refreshVersion]);
+ const offset = (new Date(month + '-01T00:00:00Z').getUTCDay() + 6) % 7;
+ const day = days.find((v) => v.date === selected);
+ return (
+
+
+ {tr('收支日历')}
+ {
+ if (e.target.value) {
+ setMonth(e.target.value);
+ setSelected(
+ e.target.value === today().slice(0, 7) ? today() : e.target.value + '-01',
+ );
+ }
+ }}
+ />
+
+
+ {tr('按账户余额变动估算;转账、借贷本金、初始余额和估值不计入。负债账户按相反方向计算。')}
+
+
+ {['一', '二', '三', '四', '五', '六', '日'].map((v) => (
+
+ {tr(v)}
+
+ ))}
+ {Array.from({ length: offset }, (_, i) => (
+
+ ))}
+ {days.map((d) => (
+
+ ))}
+
+
+
+
+ {selected} · {tr('当日收支')}
+
+
+
+ {day && (
+
+ {tr('收入')}:{day.complete ? money(day.income, base) : tr('缺少汇率')} ·{' '}
+
+ {tr('支出')}:{day.complete ? money(day.expense, base) : tr('缺少汇率')}
+
+
+ )}
+ {loading ? (
+ {tr('正在刷新数据…')}
+ ) : rows.length ? (
+ rows.map((v) => (
+
+
+ {v.name}
+ {v.notes}
+
+
+ {v.direction === 'expense' ? '−' : '+'}
+ {money(v.amount, v.currency)}
+
+ {v.converted === null && {tr('缺少汇率')}}
+
+ ))
+ ) : (
+ {tr('当天没有收支记录')}
+ )}
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+}
diff --git a/apps/web/src/DebtPaymentForm.tsx b/apps/web/src/DebtPaymentForm.tsx
index b94c3a2..13370d9 100644
--- a/apps/web/src/DebtPaymentForm.tsx
+++ b/apps/web/src/DebtPaymentForm.tsx
@@ -1,5 +1,6 @@
+import { AccountPicker } from './AccountPicker';
import { useState, type FormEvent } from 'react';
-import { type Position, nowMinute, money, requestUuid } from './api';
+import { type Position, nowMinute, money, requestUuid, positionAmount } from './api';
import { t as tr } from './i18n';
export type DebtOperation = 'borrow' | 'lend' | 'collect' | 'repay';
export const operationLabel = (operation: string) =>
@@ -8,8 +9,8 @@ export const operationLabel = (operation: string) =>
{
borrow: '借入到账',
lend: '借出付款',
- collect: '收回借款',
- repay: '偿还债务',
+ collect: '收款',
+ repay: '还款',
transfer: '账户转账',
} as Record
)[operation] || operation,
@@ -63,8 +64,8 @@ export function DebtPaymentForm({
>
{debts.map((p) => (
))}
@@ -85,17 +86,12 @@ export function DebtPaymentForm({
{tr('账户与债务同时记账并自动建立关联;这里填写本次金额,不是操作后的余额。')}
@@ -139,7 +135,7 @@ export function DebtPaymentForm({
{tr('手续费只影响账户余额:付款时额外扣除,收款时从到账金额扣除;负数表示优惠。')}
|