mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-29 11:04:12 -05:00
* Add tooltip to imported payee column in rule result window The imported payee column in SimpleTransactionsTable was missing a title attribute, so truncated text had no tooltip on hover. Other columns (category, account, notes) already pass title for this purpose. Fixes #7003 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add release notes for #7031 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Your Name <your-email@example.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
278 lines
7.6 KiB
TypeScript
278 lines
7.6 KiB
TypeScript
import React, { memo, useCallback, useMemo } from 'react';
|
|
import type { CSSProperties, ReactNode } from 'react';
|
|
import { Trans, useTranslation } from 'react-i18next';
|
|
|
|
import { SvgArrowsSynchronize } from '@actual-app/components/icons/v2';
|
|
import { theme } from '@actual-app/components/theme';
|
|
import {
|
|
format as formatDate,
|
|
isValid as isDateValid,
|
|
parseISO,
|
|
} from 'date-fns';
|
|
|
|
import * as monthUtils from 'loot-core/shared/months';
|
|
import type { TransactionEntity } from 'loot-core/types/models';
|
|
|
|
import { FinancialText } from '@desktop-client/components/FinancialText';
|
|
import {
|
|
Cell,
|
|
Field,
|
|
Row,
|
|
SelectCell,
|
|
Table,
|
|
} from '@desktop-client/components/table';
|
|
import { DisplayId } from '@desktop-client/components/util/DisplayId';
|
|
import { useAccount } from '@desktop-client/hooks/useAccount';
|
|
import { useCategory } from '@desktop-client/hooks/useCategory';
|
|
import { useDateFormat } from '@desktop-client/hooks/useDateFormat';
|
|
import { useFormat } from '@desktop-client/hooks/useFormat';
|
|
import type { FormatType } from '@desktop-client/hooks/useFormat';
|
|
import {
|
|
useSelectedDispatch,
|
|
useSelectedItems,
|
|
} from '@desktop-client/hooks/useSelected';
|
|
|
|
function serializeTransaction(
|
|
transaction: TransactionEntity,
|
|
dateFormat: string,
|
|
): TransactionEntity {
|
|
let { date } = transaction;
|
|
|
|
if (!isDateValid(parseISO(date))) {
|
|
date = monthUtils.currentDay();
|
|
}
|
|
|
|
return {
|
|
...transaction,
|
|
date: formatDate(parseISO(date), dateFormat),
|
|
};
|
|
}
|
|
|
|
type TransactionRowProps = {
|
|
transaction: TransactionEntity;
|
|
fields: string[];
|
|
selected: boolean;
|
|
format: (value: unknown, type: FormatType) => string;
|
|
};
|
|
|
|
const TransactionRow = memo(function TransactionRow({
|
|
transaction,
|
|
fields,
|
|
selected,
|
|
format,
|
|
}: TransactionRowProps) {
|
|
const { t } = useTranslation();
|
|
|
|
const { data: category } = useCategory(transaction.category);
|
|
const account = useAccount(transaction.account);
|
|
|
|
const dispatchSelected = useSelectedDispatch();
|
|
|
|
return (
|
|
<Row style={{ color: theme.tableText }}>
|
|
<SelectCell
|
|
exposed
|
|
focused={false}
|
|
onSelect={e => {
|
|
dispatchSelected({
|
|
type: 'select',
|
|
id: transaction.id,
|
|
isRangeSelect: e.shiftKey,
|
|
});
|
|
}}
|
|
selected={selected}
|
|
/>
|
|
{fields.map((field, i) => {
|
|
switch (field) {
|
|
case 'date':
|
|
return (
|
|
<Field key={i} width={100}>
|
|
{transaction.date}
|
|
</Field>
|
|
);
|
|
case 'imported_payee':
|
|
return (
|
|
<Field key={i} width="flex" title={transaction.imported_payee}>
|
|
{transaction.imported_payee}
|
|
</Field>
|
|
);
|
|
case 'payee':
|
|
return (
|
|
<Cell
|
|
key={i}
|
|
width="flex"
|
|
exposed
|
|
style={{
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'flex-start',
|
|
}}
|
|
>
|
|
{() => (
|
|
<>
|
|
{transaction.schedule && (
|
|
<SvgArrowsSynchronize
|
|
style={{
|
|
width: 13,
|
|
height: 13,
|
|
margin: '0 5px',
|
|
}}
|
|
/>
|
|
)}
|
|
{transaction.payee && (
|
|
<DisplayId type="payees" id={transaction.payee} />
|
|
)}
|
|
</>
|
|
)}
|
|
</Cell>
|
|
);
|
|
case 'category':
|
|
return (
|
|
<Field key={i} width="flex" title={category?.name}>
|
|
{category?.name || ''}
|
|
</Field>
|
|
);
|
|
case 'account':
|
|
return (
|
|
<Field
|
|
key={i}
|
|
width="flex"
|
|
title={account?.name || t('No account')}
|
|
>
|
|
{account?.name || t('No account')}
|
|
</Field>
|
|
);
|
|
case 'notes':
|
|
return (
|
|
<Field key={i} width="flex" title={transaction.notes}>
|
|
{transaction.notes}
|
|
</Field>
|
|
);
|
|
case 'amount':
|
|
return (
|
|
<Field key={i} width={75} style={{ textAlign: 'right' }}>
|
|
<FinancialText>
|
|
{format(transaction.amount, 'financial')}
|
|
</FinancialText>
|
|
</Field>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
})}
|
|
</Row>
|
|
);
|
|
});
|
|
|
|
type SimpleTransactionsTableProps = {
|
|
transactions: readonly TransactionEntity[];
|
|
renderEmpty: ReactNode;
|
|
fields?: string[];
|
|
style?: CSSProperties;
|
|
};
|
|
|
|
export function SimpleTransactionsTable({
|
|
transactions,
|
|
renderEmpty,
|
|
fields = ['date', 'payee', 'amount'],
|
|
style,
|
|
}: SimpleTransactionsTableProps) {
|
|
const format = useFormat();
|
|
const dateFormat = useDateFormat() || 'MM/dd/yyyy';
|
|
const selectedItems = useSelectedItems();
|
|
const dispatchSelected = useSelectedDispatch();
|
|
const memoFields = useMemo(() => fields, [fields]);
|
|
|
|
const serializedTransactions = useMemo(() => {
|
|
return transactions.map(trans => serializeTransaction(trans, dateFormat));
|
|
}, [transactions, dateFormat]);
|
|
|
|
const renderItem = useCallback(
|
|
({ item }: { item: TransactionEntity }) => {
|
|
return (
|
|
<TransactionRow
|
|
transaction={item}
|
|
fields={memoFields}
|
|
selected={selectedItems && selectedItems.has(item.id)}
|
|
format={format}
|
|
/>
|
|
);
|
|
},
|
|
[memoFields, selectedItems, format],
|
|
);
|
|
|
|
return (
|
|
<Table
|
|
style={style}
|
|
backgroundColor={theme.tableBackground}
|
|
items={serializedTransactions}
|
|
renderEmpty={renderEmpty}
|
|
headers={
|
|
<>
|
|
<SelectCell
|
|
exposed
|
|
focused={false}
|
|
selected={selectedItems.size > 0}
|
|
width={20}
|
|
onSelect={e =>
|
|
dispatchSelected({
|
|
type: 'select-all',
|
|
isRangeSelect: e.shiftKey,
|
|
})
|
|
}
|
|
/>
|
|
{fields.map((field, i) => {
|
|
switch (field) {
|
|
case 'date':
|
|
return (
|
|
<Field key={i} width={100}>
|
|
<Trans>Date</Trans>
|
|
</Field>
|
|
);
|
|
case 'imported_payee':
|
|
return (
|
|
<Field key={i} width="flex">
|
|
<Trans>Imported payee</Trans>
|
|
</Field>
|
|
);
|
|
case 'payee':
|
|
return (
|
|
<Field key={i} width="flex">
|
|
<Trans>Payee</Trans>
|
|
</Field>
|
|
);
|
|
case 'category':
|
|
return (
|
|
<Field key={i} width="flex">
|
|
<Trans>Category</Trans>
|
|
</Field>
|
|
);
|
|
case 'account':
|
|
return (
|
|
<Field key={i} width="flex">
|
|
<Trans>Account</Trans>
|
|
</Field>
|
|
);
|
|
case 'notes':
|
|
return (
|
|
<Field key={i} width="flex">
|
|
<Trans>Notes</Trans>
|
|
</Field>
|
|
);
|
|
case 'amount':
|
|
return (
|
|
<Field key={i} width={75} style={{ textAlign: 'right' }}>
|
|
<Trans>Amount</Trans>
|
|
</Field>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
})}
|
|
</>
|
|
}
|
|
renderItem={renderItem}
|
|
/>
|
|
);
|
|
}
|