Add support for existing transactions

This commit is contained in:
2025-05-21 22:52:48 +02:00
parent e1dc42c254
commit cc208dd748
9 changed files with 275 additions and 30 deletions

View File

@@ -3,11 +3,13 @@ import { useStore } from "../../store/AppStore";
import TransactionsTable from "./TransactionsTable";
import { SkippedLines } from "./SkippedLines";
import ImportBar from "./ImportBar";
import type { ImportOptions } from "../../types/api";
import { submitTransactions } from "../../services/api.service";
import { type ImportOptions, type Transaction } from "../../types/api";
import { getDateRangedTransactions, getLatestTransactions, submitTransactions } from "../../services/api.service";
import { useLoader } from "../../hooks/useLoader";
import { FilterPanel, type FilterData } from "./FilterPanel";
import dayjs from "dayjs";
import { ExistingTransactions } from "./ExistingTransactions";
import classNames from "classnames";
export default function PrepareTransactionsPage() {
@@ -15,6 +17,8 @@ export default function PrepareTransactionsPage() {
const [userFilter, setUserFilter] = useState<FilterData>({});
const [deselected, setDeselected] = useState<number[]>([]);
const [enabledExistingTransactions, setEnabledExistingTransactions] = useState(true);
const [existingTransactions, setExistingTransactions] = useState<Transaction[]>([]);
const select = useCallback((index: number) => setDeselected(deselected.filter(x => x !== index)), [deselected, setDeselected]);
const deselect = useCallback((index: number) => setDeselected([...deselected, index]), [deselected, setDeselected]);
@@ -38,9 +42,12 @@ export default function PrepareTransactionsPage() {
return date.isBefore(userFilter.to, 'day') || date.isSame(userFilter.to, 'day');
})
.slice().reverse()
, [state.transactions, userFilter]);
const selectedTransactions = useMemo(() => filteredTransactions.filter((_, i) => !deselected.includes(i)), [filteredTransactions, deselected]);
const selectedTransactions = useMemo(() =>
filteredTransactions.filter((_, i) => !deselected.includes(i)),
[filteredTransactions, deselected]);
const { fn: handleSubmit, loading } = useLoader(async (opts: ImportOptions) => {
if(!state.server) {
@@ -60,9 +67,36 @@ export default function PrepareTransactionsPage() {
offset: 1
}
});
}, [selectedTransactions, state]);
}, [selectedTransactions, state]);
useEffect(() => setDeselected([]), [filteredTransactions]);
const { fn: refreshExistingTransactions, loading: loadingExistingTransactions } = useLoader(async () => {
if (!state.server || !enabledExistingTransactions) {
setExistingTransactions([]);
return;
}
// If no "from" filter active, pull the latest 10 transactions
if (!userFilter.from) {
const transactions = await getLatestTransactions(state.server, 10);
setExistingTransactions(transactions);
return;
}
const start = userFilter.from.format("YYYY-MM-DD");
const end = (userFilter.to ?? dayjs()).format("YYYY-MM-DD");
const transactions = await getDateRangedTransactions(state.server, start, end);
setExistingTransactions(transactions);
}, [enabledExistingTransactions, state.server, userFilter]);
useEffect(() => {
setDeselected([])
}, [filteredTransactions]);
useEffect(() => {
refreshExistingTransactions()
}, [enabledExistingTransactions, state.server, userFilter]);
return (
<div className="columns mt-6">
@@ -74,15 +108,47 @@ export default function PrepareTransactionsPage() {
<h3 className="title is-5">Filters</h3>
<FilterPanel value={userFilter} setValue={setUserFilter} />
</div>
<div className="content">
<h3 className="title is-5">Considered transactions ({selectedTransactions.length}/{filteredTransactions.length})</h3>
<TransactionsTable
transactions={filteredTransactions}
deselected={deselected}
deselect={deselect}
select={select} />
</div>
<div className="field">
<label className="label">Fetch existing transactions</label>
<div className="notification is-info is-light">
If enabled, the existing transactions will be fetched automatically basing on date filter.
If "from" is filled, the existing transactions will be fetched basing on set date (if "to" is not filled, the todays date will be considered).
Otherwise, the latest <abbr title="Transaction before transfer consolidation.">10 raw transactions</abbr> will be fetched. Because of transfer consolidation,
the ultimate number of transactions can be lower.
</div>
<div className="control">
<div className="buttons has-addons">
<button
className={classNames('button', { 'is-selected': enabledExistingTransactions, 'is-success': enabledExistingTransactions})}
onClick={() => setEnabledExistingTransactions(true)}
disabled={enabledExistingTransactions}>I</button>
<button
className={classNames('button', { 'is-selected': !enabledExistingTransactions, 'is-danger': !enabledExistingTransactions})}
onClick={() => setEnabledExistingTransactions(false)}
disabled={!enabledExistingTransactions}>O</button>
</div>
</div>
</div>
<div className="columns">
<div className={classNames('column', { 'is-6': enabledExistingTransactions, 'is-12': !enabledExistingTransactions })}>
<h3 className="title is-5">Considered transactions ({selectedTransactions.length}/{filteredTransactions.length})</h3>
<TransactionsTable
transactions={filteredTransactions}
deselected={deselected}
deselect={deselect}
select={select} />
</div>
{enabledExistingTransactions &&
<div className="column is-6">
<h3 className="title is-5">Existing transactions ({existingTransactions.length})</h3>
<ExistingTransactions
loading={loadingExistingTransactions}
transactions={existingTransactions} />
</div>}
</div>
<div className="content">
<h3 className="title is-5">Skipped lines ({state.skipped.length})</h3>
@@ -92,7 +158,6 @@ export default function PrepareTransactionsPage() {
<div className="content">
<ImportBar loading={loading} onSubmit={handleSubmit} onStartOver={() => location.reload()}/>
</div>
</div>
</div>
</div>