From 03b8e94dcb275272df3569de30eb3d87a6ef1ed4 Mon Sep 17 00:00:00 2001 From: Kyle Baley Date: Tue, 17 Dec 2024 18:57:22 -0500 Subject: [PATCH] Add support for tags in transactions Related to #76 --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/kbaley/Coronado/issues/76?shareId=XXXX-XXXX-XXXX-XXXX). --- .../Commands/NewTransaction.cs | 25 +++++++++++++++++ .../ClientApp/src/api/transactionApi.js | 2 +- .../account_page/EditableTransaction.js | 27 ++++++++++++++++++- .../account_page/TransactionList.js | 9 +++++-- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/Coronado.ConsoleApp/Commands/NewTransaction.cs b/Coronado.ConsoleApp/Commands/NewTransaction.cs index d7327c35..b62e9883 100644 --- a/Coronado.ConsoleApp/Commands/NewTransaction.cs +++ b/Coronado.ConsoleApp/Commands/NewTransaction.cs @@ -32,6 +32,12 @@ public async Task Execute(Datastore context, params string[] args) ReadLine.AutoCompletionHandler = null; return; }; + ReadLine.AutoCompletionHandler = new TagCompletionHandler(context); + if (!GetInput("Tags (comma-separated)", out var tags)) + { + ReadLine.AutoCompletionHandler = null; + return; + }; ReadLine.AutoCompletionHandler = null; if (!GetInput("Description", out var description)) return; if (!GetInput("Amount", out var amount)) return; @@ -41,6 +47,7 @@ public async Task Execute(Datastore context, params string[] args) TransactionDate = DateTime.Parse(date), Vendor = vendor, CategoryName = category, + Tags = tags.Split(',').Select(tag => tag.Trim()).ToList(), Amount = decimal.Parse(amount), Description = description, AccountId = context.SelectedAccount.AccountId, @@ -115,4 +122,22 @@ public string[] GetSuggestions(string text, int index) return null; } } + + class TagCompletionHandler : IAutoCompleteHandler + { + private readonly Datastore _context; + + public TagCompletionHandler(Datastore context) : base() + { + _context = context; + } + + public char[] Separators { get; set; } = new char[] { '\t' }; + public string[] GetSuggestions(string text, int index) + { + var tags = _context.Transactions.SelectMany(t => t.Tags).Where(tag => tag.Contains(text)).Distinct(); + if (tags.Any()) return tags.ToArray(); + return null; + } + } } diff --git a/Coronado.Web/ClientApp/src/api/transactionApi.js b/Coronado.Web/ClientApp/src/api/transactionApi.js index 2df8825b..8cd7c3ae 100644 --- a/Coronado.Web/ClientApp/src/api/transactionApi.js +++ b/Coronado.Web/ClientApp/src/api/transactionApi.js @@ -74,4 +74,4 @@ class TransactionApi { } } -export default TransactionApi; \ No newline at end of file +export default TransactionApi; diff --git a/Coronado.Web/ClientApp/src/components/account_page/EditableTransaction.js b/Coronado.Web/ClientApp/src/components/account_page/EditableTransaction.js index 7c052509..8540d42e 100644 --- a/Coronado.Web/ClientApp/src/components/account_page/EditableTransaction.js +++ b/Coronado.Web/ClientApp/src/components/account_page/EditableTransaction.js @@ -8,6 +8,7 @@ import { IconButton, makeStyles, TableRow, TableCell } from '@material-ui/core'; import CancelIcon from '@material-ui/icons/Cancel' import moment from 'moment'; import { TransactionInput } from '../TransactionInput'; +import Autocomplete from '@material-ui/lab/Autocomplete'; const styles = theme => ({ input: { @@ -36,6 +37,7 @@ export default function EditableTransaction(props) { debit: props.transaction.debit ? Number(props.transaction.debit).toFixed(2) : '', credit: props.transaction.credit ? Number(props.transaction.credit).toFixed(2) : '', categoryName: props.transaction.categoryDisplay, + tags: props.transaction.tags || [], }); const vendors = useSelector(state => state.vendors); const categories = useSelector(state => getCategoriesForDropdown(state.categories, state.accounts, state.invoices)); @@ -53,6 +55,7 @@ export default function EditableTransaction(props) { debit: transaction.debit ? Number(transaction.debit).toFixed(2) : '', credit: transaction.credit ? Number(transaction.credit).toFixed(2) : '', categoryName: transaction.categoryDisplay, + tags: transaction.tags || [], }); setSelectedCategory({ categoryId: transaction.categoryId, @@ -139,6 +142,13 @@ export default function EditableTransaction(props) { } } + const handleChangeTags = (event, newValue) => { + setTrx({ + ...trx, + tags: newValue, + }); + } + const updateTransaction = () => { dispatch(transactionActions.updateTransaction(trx)); props.onFinishEdit(); @@ -206,7 +216,22 @@ export default function EditableTransaction(props) { onKeyPress={handleKeyPress} /> - + + ( + + )} + /> + ); } diff --git a/Coronado.Web/ClientApp/src/components/account_page/TransactionList.js b/Coronado.Web/ClientApp/src/components/account_page/TransactionList.js index 0a1a4bdd..eda0c489 100644 --- a/Coronado.Web/ClientApp/src/components/account_page/TransactionList.js +++ b/Coronado.Web/ClientApp/src/components/account_page/TransactionList.js @@ -39,6 +39,10 @@ const styles = theme => ({ width: 100, maxWidth: 100, }, + "& td:nth-child(9)": { + width: 200, + maxWidth: 200, + }, "& input": { width: "100%", } @@ -69,13 +73,14 @@ export default function TransactionList(props) { Description Debit Credit + Tags - {isLoading ? : + {isLoading ? : transactions.map(trx => ); -} \ No newline at end of file +}