feat: Improve Balance View category breakdown with paid/expected distinction #241
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
nidde/parenting-tool!241
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "copilot/improve-balance-view-presentation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The Balance View category totals showed only the actualized/paid sum when collapsed, making it impossible to see the true projected spend for a category at a glance. When expanded, paid and unpaid transactions were visually identical.
Description
Changes are isolated to
CategorySummaryList.vue— no backend or data-shape changes required. The existing transaction DTO fields (actual_amount,expected_amount,is_virtual,is_deferred) already carry everything needed.New totals model per category
Replaced the single
actualTotal/expectedTotalpair with three purpose-built values:paidTotal— sum ofactual_amountfor settled transactionsexpectedRemainingTotal— sum ofexpected_amountfor unpaid/virtual transactionsbudgetTotal=paidTotal + expectedRemainingTotal— effective projected spendCollapsed category header
Now leads with
budgetTotaland surfaces contextual chips:Paid: $X(green) when any items are settledExpected: $Y(blue) when any items are outstandingIndividual transaction rows
State is now visually unambiguous:
Category total & Grand total
Both show the three-part breakdown:
budgetTotalheadline + "Paid: X" + "Expected: Y" sub-lines.Sorting
Changed from
actualTotaltobudgetTotalso categories with large unpaid balances rank correctly.TypeScript & code quality improvements (based on review feedback)
BalanceCategoryTransaction,EnrichedTransaction, andCategorySummaryinterfaces — allanytypes removed; the groupingMapis fully typed asMap<string, CategorySummary>isTransactionPaid()is now evaluated once per transaction duringcategorySummariescomputation and stored aspaid: booleanon eachEnrichedTransaction; the template referencestransaction.paidthroughout with no repeated calls||fallbacks with??(nullish coalescing) soexpected_amount = 0is treated correctly instead of falling back toamountparseFloat(String(x))patterns with the cleanerNumber(x)>text node riskType of Change
Testing
Code Quality
CI/CD Checks
Additional Notes
All changes are confined to
CategorySummaryList.vue. The component now enforces TypeScript contracts on the transaction and category summary shapes, making future modifications to the paid/expected logic less error-prone.Comment
@coderabbitai helpto get the list of available commands and usage tips.Pull request overview
This PR improves the Balance View’s category and transaction presentation by distinguishing paid vs expected amounts, and by basing collapsed totals/sorting on an effective “budget” total rather than only actualized spend.
Changes:
paidTotal,expectedRemainingTotal, andbudgetTotal, with new grand totals derived from them.budgetTotal.Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 14 comments.
Show a summary per file
totalDaysassignment.@ -8,0 +17,4 @@}interface BalanceCategoryTransaction {id?: string | number;New logic adds more fields and rules but relies on
anyfor bothtransactionand the grouped summary model, which makes it easier to introduce regressions (e.g., mistyped field names likeactual_amount/expected_amount, or missing booleans). Define (or reuse) aTransactiontype and aCategorySummarytype (and type the map asMap<string, CategorySummary>) so TypeScript can enforce the new totals contract.New logic adds more fields and rules but relies on
anyfor bothtransactionand the grouped summary model, which makes it easier to introduce regressions (e.g., mistyped field names likeactual_amount/expected_amount, or missing booleans). Define (or reuse) aTransactiontype and aCategorySummarytype (and type the map asMap<string, CategorySummary>) so TypeScript can enforce the new totals contract.@ -8,0 +41,4 @@/** Sum of actual_amount for paid/actualized items */paidTotal: number;/** Sum of expected_amount for unpaid/virtual items */expectedRemainingTotal: number;transaction.expected_amount || transaction.amountincorrectly falls back whenexpected_amountis0(because0is falsy), which can inflate totals and display the wrong value. Prefer nullish coalescing (or an explicit null/undefined check) so that0is treated as a valid expected amount in both the totals calculation and the transaction-row amount display.@ -83,0 +139,4 @@const grandTotalExpectedRemaining = computed(() =>categorySummaries.value.reduce((sum, c) => sum + c.expectedRemainingTotal,There are stray
>characters between sibling<v-chip>elements (e.g.,</v-chipthen> <v-chip). In Vue templates this can render a literal>text node in the UI. Remove those extra>separators and place sibling components adjacently as proper elements (newline/whitespace only) to avoid unwanted characters in the rendered output.There are stray
>characters between sibling<v-chip>elements (e.g.,</v-chipthen> <v-chip). In Vue templates this can render a literal>text node in the UI. Remove those extra>separators and place sibling components adjacently as proper elements (newline/whitespace only) to avoid unwanted characters in the rendered output.@ -133,3 +189,1 @@> {{ getPercentage(category.actualTotal) }}%</v-chip>transaction.expected_amount || transaction.amountincorrectly falls back whenexpected_amountis0(because0is falsy), which can inflate totals and display the wrong value. Prefer nullish coalescing (or an explicit null/undefined check) so that0is treated as a valid expected amount in both the totals calculation and the transaction-row amount display.There are stray
>characters between sibling<v-chip>elements (e.g.,</v-chipthen> <v-chip). In Vue templates this can render a literal>text node in the UI. Remove those extra>separators and place sibling components adjacently as proper elements (newline/whitespace only) to avoid unwanted characters in the rendered output.There are stray
>characters between sibling<v-chip>elements (e.g.,</v-chipthen> <v-chip). In Vue templates this can render a literal>text node in the UI. Remove those extra>separators and place sibling components adjacently as proper elements (newline/whitespace only) to avoid unwanted characters in the rendered output.There are stray
>characters between sibling<v-chip>elements (e.g.,</v-chipthen> <v-chip). In Vue templates this can render a literal>text node in the UI. Remove those extra>separators and place sibling components adjacently as proper elements (newline/whitespace only) to avoid unwanted characters in the rendered output.There are stray
>characters between sibling<v-chip>elements (e.g.,</v-chipthen> <v-chip). In Vue templates this can render a literal>text node in the UI. Remove those extra>separators and place sibling components adjacently as proper elements (newline/whitespace only) to avoid unwanted characters in the rendered output.isPaid(transaction)is called multiple times per row in the template, which duplicates logic and makes future changes more error-prone (e.g., if the paid criteria changes). Consider computing it once per transaction row (e.g., derive apaidboolean when buildingcategorySummaries, or bind via a small wrapper/helper for the template) and reference that single value throughout.isPaid(transaction)is called multiple times per row in the template, which duplicates logic and makes future changes more error-prone (e.g., if the paid criteria changes). Consider computing it once per transaction row (e.g., derive apaidboolean when buildingcategorySummaries, or bind via a small wrapper/helper for the template) and reference that single value throughout.isPaid(transaction)is called multiple times per row in the template, which duplicates logic and makes future changes more error-prone (e.g., if the paid criteria changes). Consider computing it once per transaction row (e.g., derive apaidboolean when buildingcategorySummaries, or bind via a small wrapper/helper for the template) and reference that single value throughout.isPaid(transaction)is called multiple times per row in the template, which duplicates logic and makes future changes more error-prone (e.g., if the paid criteria changes). Consider computing it once per transaction row (e.g., derive apaidboolean when buildingcategorySummaries, or bind via a small wrapper/helper for the template) and reference that single value throughout.@copilot apply changes based on the comments in this thread
Applied all four reviewer feedback items in commit
ebd2a6a:BalanceCategoryTransaction,EnrichedTransaction, andCategorySummaryinterfaces; theMapis now typed asMap<string, CategorySummary>and the prop isBalanceCategoryTransaction[]instead ofany[]paidper transaction —isTransactionPaid()is now called once per transaction in the script (const paid = isTransactionPaid(transaction)), stored on eachEnrichedTransaction, and the template referencestransaction.paidthroughout — zeroisPaid()calls remain in the template||→??— Allexpected_amount || amountfallbacks are nowexpected_amount ?? amountso a zero expected amount is handled correctly; also cleaned upparseFloat(String(x))to the simplerNumber(x)per follow-up review feedback>chips — Collapsed header chips rewritten to use standard</v-chip>closing tags; linter (Prettier + prettier-plugin-vue) confirmed cleanPull request closed