mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-28 18:40:34 -05:00
Format notes as clickable tags
This commit is contained in:
@@ -1457,7 +1457,6 @@ class AccountInternal extends PureComponent {
|
||||
addNotification,
|
||||
accountsSyncing,
|
||||
failedAccounts,
|
||||
pushModal,
|
||||
replaceModal,
|
||||
showExtraBalances,
|
||||
accountId,
|
||||
@@ -1622,7 +1621,6 @@ class AccountInternal extends PureComponent {
|
||||
</View>
|
||||
) : null
|
||||
}
|
||||
pushModal={pushModal}
|
||||
onSort={this.onSort}
|
||||
sortField={this.state.sort.field}
|
||||
ascDesc={this.state.sort.ascDesc}
|
||||
|
||||
@@ -136,7 +136,9 @@ type CellProps = Omit<ComponentProps<typeof View>, 'children' | 'value'> & {
|
||||
plain?: boolean;
|
||||
exposed?: boolean;
|
||||
children?: ReactNode | (() => ReactNode);
|
||||
unexposedContent?: ReactNode;
|
||||
unexposedContent?: (
|
||||
props: ComponentProps<typeof UnexposedCellContent>,
|
||||
) => ReactNode;
|
||||
value?: string;
|
||||
valueStyle?: CSSProperties;
|
||||
onExpose?: (name: string) => void;
|
||||
@@ -228,7 +230,9 @@ export function Cell({
|
||||
}
|
||||
}
|
||||
>
|
||||
{unexposedContent || (
|
||||
{unexposedContent ? (
|
||||
unexposedContent({ value, formatter })
|
||||
) : (
|
||||
<UnexposedCellContent value={value} formatter={formatter} />
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { useRef, useCallback, useLayoutEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import {
|
||||
splitTransaction,
|
||||
@@ -76,7 +78,6 @@ export function TransactionList({
|
||||
dateFormat,
|
||||
hideFraction,
|
||||
addNotification,
|
||||
pushModal,
|
||||
renderEmpty,
|
||||
onSort,
|
||||
sortField,
|
||||
@@ -88,6 +89,7 @@ export function TransactionList({
|
||||
}) {
|
||||
const transactionsLatest = useRef();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
transactionsLatest.current = transactions;
|
||||
@@ -161,13 +163,12 @@ export function TransactionList({
|
||||
});
|
||||
|
||||
const onNavigateToSchedule = useCallback(scheduleId => {
|
||||
pushModal('schedule-edit', { id: scheduleId });
|
||||
dispatch(pushModal('schedule-edit', { id: scheduleId }));
|
||||
});
|
||||
|
||||
return (
|
||||
<TransactionTable
|
||||
ref={tableRef}
|
||||
pushModal={pushModal}
|
||||
transactions={allTransactions}
|
||||
loadMoreTransactions={loadMoreTransactions}
|
||||
accounts={accounts}
|
||||
|
||||
@@ -10,6 +10,7 @@ import React, {
|
||||
useLayoutEffect,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import {
|
||||
format as formatDate,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
isValid as isDateValid,
|
||||
} from 'date-fns';
|
||||
|
||||
import { pushModal } from 'loot-core/client/actions';
|
||||
import { useCachedSchedules } from 'loot-core/src/client/data-hooks/schedules';
|
||||
import {
|
||||
getAccountsById,
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
} from 'loot-core/src/shared/util';
|
||||
|
||||
import { useMergedRefs } from '../../hooks/useMergedRefs';
|
||||
import { useNavigate } from '../../hooks/useNavigate';
|
||||
import { usePrevious } from '../../hooks/usePrevious';
|
||||
import { useSelectedDispatch, useSelectedItems } from '../../hooks/useSelected';
|
||||
import { useSplitsExpanded } from '../../hooks/useSplitsExpanded';
|
||||
@@ -413,7 +416,8 @@ function HeaderCell({
|
||||
width={width}
|
||||
name={id}
|
||||
alignItems={alignItems}
|
||||
unexposedContent={
|
||||
value={value}
|
||||
unexposedContent={({ value: cellValue }) => (
|
||||
<Button
|
||||
type="bare"
|
||||
onClick={onClick}
|
||||
@@ -427,7 +431,7 @@ function HeaderCell({
|
||||
marginRight,
|
||||
}}
|
||||
>
|
||||
<UnexposedCellContent value={value} />
|
||||
<UnexposedCellContent value={cellValue} />
|
||||
{icon === 'asc' && (
|
||||
<SvgArrowDown width={10} height={10} style={{ marginLeft: 5 }} />
|
||||
)}
|
||||
@@ -435,22 +439,20 @@ function HeaderCell({
|
||||
<SvgArrowUp width={10} height={10} style={{ marginLeft: 5 }} />
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PayeeCell({
|
||||
id,
|
||||
payeeId,
|
||||
accountId,
|
||||
payee,
|
||||
focused,
|
||||
inherited,
|
||||
payees,
|
||||
accounts,
|
||||
valueStyle,
|
||||
transaction,
|
||||
payee,
|
||||
transferAcct,
|
||||
isPreview,
|
||||
onEdit,
|
||||
@@ -462,16 +464,12 @@ function PayeeCell({
|
||||
}) {
|
||||
const isCreatingPayee = useRef(false);
|
||||
|
||||
// Filter out the account we're currently in as it is not a valid transfer
|
||||
accounts = accounts.filter(account => account.id !== accountId);
|
||||
payees = payees.filter(payee => payee.transfer_acct !== accountId);
|
||||
|
||||
return (
|
||||
<CustomCell
|
||||
width="flex"
|
||||
name="payee"
|
||||
textAlign="flex"
|
||||
value={payeeId}
|
||||
value={payee?.id}
|
||||
valueStyle={{
|
||||
...valueStyle,
|
||||
...(inherited && { color: theme.tableTextInactive }),
|
||||
@@ -488,7 +486,8 @@ function PayeeCell({
|
||||
isCreatingPayee.current = false;
|
||||
}
|
||||
}}
|
||||
unexposedContent={
|
||||
formatter={() => getPayeePretty(transaction, payee, transferAcct)}
|
||||
unexposedContent={props => (
|
||||
<>
|
||||
<PayeeIcons
|
||||
transaction={transaction}
|
||||
@@ -496,12 +495,9 @@ function PayeeCell({
|
||||
onNavigateToTransferAccount={onNavigateToTransferAccount}
|
||||
onNavigateToSchedule={onNavigateToSchedule}
|
||||
/>
|
||||
<UnexposedCellContent
|
||||
value={payeeId}
|
||||
formatter={() => getPayeePretty(transaction, payee, transferAcct)}
|
||||
/>
|
||||
<UnexposedCellContent {...props} />
|
||||
</>
|
||||
}
|
||||
)}
|
||||
>
|
||||
{({
|
||||
onBlur,
|
||||
@@ -515,7 +511,7 @@ function PayeeCell({
|
||||
<PayeeAutocomplete
|
||||
payees={payees}
|
||||
accounts={accounts}
|
||||
value={payeeId}
|
||||
value={payee?.id}
|
||||
shouldSaveFromKey={shouldSaveFromKey}
|
||||
inputProps={{
|
||||
onBlur,
|
||||
@@ -527,7 +523,7 @@ function PayeeCell({
|
||||
focused={true}
|
||||
onUpdate={(id, value) => onUpdate?.(value)}
|
||||
onSelect={onSave}
|
||||
onManagePayees={() => onManagePayees(payeeId)}
|
||||
onManagePayees={() => onManagePayees(payee?.id)}
|
||||
menuPortalTarget={undefined}
|
||||
/>
|
||||
);
|
||||
@@ -643,7 +639,9 @@ const Transaction = memo(function Transaction(props) {
|
||||
onNavigateToSchedule,
|
||||
} = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const dispatchSelected = useSelectedDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [prevShowZero, setPrevShowZero] = useState(showZeroInDeposit);
|
||||
const [prevTransaction, setPrevTransaction] = useState(originalTransaction);
|
||||
@@ -685,13 +683,15 @@ const Transaction = memo(function Transaction(props) {
|
||||
) {
|
||||
if (showReconciliationWarning === false) {
|
||||
setShowReconciliationWarning(true);
|
||||
props.pushModal('confirm-transaction-edit', {
|
||||
onConfirm: () => {
|
||||
setShowReconciliationWarning(false);
|
||||
onUpdateAfterConfirm(name, value);
|
||||
},
|
||||
confirmReason: 'editReconciled',
|
||||
});
|
||||
dispatch(
|
||||
pushModal('confirm-transaction-edit', {
|
||||
onConfirm: () => {
|
||||
setShowReconciliationWarning(false);
|
||||
onUpdateAfterConfirm(name, value);
|
||||
},
|
||||
confirmReason: 'editReconciled',
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
onUpdateAfterConfirm(name, value);
|
||||
@@ -700,12 +700,14 @@ const Transaction = memo(function Transaction(props) {
|
||||
|
||||
// Allow un-reconciling (unlocking) transactions
|
||||
if (name === 'cleared' && transaction.reconciled) {
|
||||
props.pushModal('confirm-transaction-edit', {
|
||||
onConfirm: () => {
|
||||
onUpdateAfterConfirm('reconciled', false);
|
||||
},
|
||||
confirmReason: 'unlockReconciled',
|
||||
});
|
||||
dispatch(
|
||||
pushModal('confirm-transaction-edit', {
|
||||
onConfirm: () => {
|
||||
onUpdateAfterConfirm('reconciled', false);
|
||||
},
|
||||
confirmReason: 'unlockReconciled',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,6 +758,18 @@ const Transaction = memo(function Transaction(props) {
|
||||
}
|
||||
}
|
||||
|
||||
const onNoteTagClick = noteTag => {
|
||||
const conditions = [
|
||||
{ field: 'notes', op: 'contains', value: noteTag, type: 'string' },
|
||||
];
|
||||
navigate('/accounts', {
|
||||
state: {
|
||||
goBack: true,
|
||||
conditions,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
id,
|
||||
amount,
|
||||
@@ -977,15 +991,14 @@ const Transaction = memo(function Transaction(props) {
|
||||
<PayeeCell
|
||||
/* Payee field for all transactions */
|
||||
id={id}
|
||||
payeeId={payeeId}
|
||||
accountId={accountId}
|
||||
payee={payee}
|
||||
focused={focusedField === 'payee'}
|
||||
inherited={inheritedFields && inheritedFields.has('payee')}
|
||||
payees={payees}
|
||||
accounts={accounts}
|
||||
/* Filter out the account we're currently in as it is not a valid transfer */
|
||||
accounts={accounts.filter(account => account.id !== accountId)}
|
||||
payees={payees.filter(payee => payee.transfer_acct !== accountId)}
|
||||
valueStyle={valueStyle}
|
||||
transaction={transaction}
|
||||
payee={payee}
|
||||
transferAcct={transferAcct}
|
||||
importedPayee={importedPayee}
|
||||
isPreview={isPreview}
|
||||
@@ -1009,7 +1022,13 @@ const Transaction = memo(function Transaction(props) {
|
||||
exposed={focusedField === 'notes'}
|
||||
focused={focusedField === 'notes'}
|
||||
value={notes || ''}
|
||||
style={{
|
||||
overflowX: 'auto',
|
||||
overflowY: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
valueStyle={valueStyle}
|
||||
formatter={value => notesTagFormatter(value, onNoteTagClick)}
|
||||
onExpose={name => !isPreview && onEdit(id, name)}
|
||||
inputProps={{
|
||||
value: notes || '',
|
||||
@@ -1626,7 +1645,6 @@ function TransactionTableInner({
|
||||
onToggleSplit={props.onToggleSplit}
|
||||
onNavigateToTransferAccount={onNavigateToTransferAccount}
|
||||
onNavigateToSchedule={onNavigateToSchedule}
|
||||
pushModal={props.pushModal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -2172,3 +2190,39 @@ export const TransactionTable = forwardRef((props, ref) => {
|
||||
});
|
||||
|
||||
TransactionTable.displayName = 'TransactionTable';
|
||||
|
||||
function notesTagFormatter(value, onNoteTagClick) {
|
||||
const words = value.split(' ');
|
||||
return (
|
||||
<>
|
||||
{words.map((word, i) => {
|
||||
if (word.startsWith('#') && word.length > 1) {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="bare"
|
||||
key={i}
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
padding: 4,
|
||||
borderRadius: 16,
|
||||
backgroundColor: theme.pillBackground,
|
||||
userSelect: 'none',
|
||||
color: theme.pillText,
|
||||
}}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onNoteTagClick?.(word);
|
||||
}}
|
||||
>
|
||||
{word}
|
||||
</Button>{' '}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return `${word} `;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,11 +176,11 @@ export const checkboxBorderSelected = colorPalette.purple300;
|
||||
export const checkboxShadowSelected = colorPalette.purple500;
|
||||
export const checkboxToggleBackground = colorPalette.gray700;
|
||||
|
||||
export const pillBackground = colorPalette.navy800;
|
||||
export const pillBackground = colorPalette.navy600;
|
||||
export const pillBackgroundLight = colorPalette.navy900;
|
||||
export const pillText = colorPalette.navy200;
|
||||
export const pillTextHighlighted = colorPalette.purple200;
|
||||
export const pillBorder = colorPalette.navy700;
|
||||
export const pillBorder = colorPalette.navy600;
|
||||
export const pillBorderDark = pillBorder;
|
||||
export const pillBackgroundSelected = colorPalette.purple600;
|
||||
export const pillTextSelected = colorPalette.navy150;
|
||||
|
||||
Reference in New Issue
Block a user