mirror of
https://github.com/we-promise/sure.git
synced 2026-04-09 07:14:47 +00:00
* feat: Add subcategory breakdown to Cash Flow and Reports Implements Discussion #546 - adds hierarchical category/subcategory visualization to both the Sankey chart and Reports breakdown tables. Sankey chart changes: - Income: subcategory → parent category → Cash Flow - Expense: Cash Flow → parent category → subcategory - Extracted process_category_totals helper to DRY up income/expense logic Reports breakdown changes: - Subcategories display nested under parent categories - Smaller dots and indented rows for visual hierarchy - Extracted _breakdown_table partial to eliminate duplication * fix: Dynamic node padding for Sankey chart with many nodes - Add dynamic nodePadding calculation to prevent padding from dominating chart height when there are many subcategory nodes - Extract magic numbers to static constants for configuration - Decompose monolithic #draw() into focused methods - Consolidate duplicate tooltip/currency formatting code - Modernize syntax with spread operators and optional chaining * fix: Hide overlapping Sankey labels, show on hover - Add label overlap detection by grouping nodes by column depth - Hide labels that would overlap with adjacent nodes - Show hidden labels on hover (node rectangle or connected links) - Add hover events to node rectangles (not just text) * fix: Use deterministic fallback colors for categories - Replace Category::COLORS.sample with Category::UNCATEGORIZED_COLOR for income categories in Sankey chart (was producing different colors on each page load) - Add nil color fallback in reports_controller for parent and root categories Addresses CodeRabbit review feedback. * fix: Expand CSS variable map for d3 color manipulation Add hex mappings for commonly used CSS variables so d3 can manipulate opacity for gradients and hover effects: - var(--color-destructive) -> #EC2222 - var(--color-gray-400) -> #9E9E9E - var(--color-gray-500) -> #737373 * test: Add tests for subcategory breakdown in dashboard and reports - Test dashboard renders Sankey chart with parent/subcategory transactions - Test reports groups transactions by parent and subcategories - Test reports handles categories with nil colors - Use EntriesTestHelper#create_transaction for cleaner test setup * Fix lint: use Number.NEGATIVE_INFINITY * Remove obsolete nil color test Category model now validates color presence, so nil color categories cannot exist. The fallback handling in reports_controller is still in place but the scenario is unreachable. * Update reports_controller.rb * FIX trade category --------- Co-authored-by: sokie <sokysrm@gmail.com>
86 lines
4.3 KiB
Plaintext
86 lines
4.3 KiB
Plaintext
<%# Renders a breakdown table for income or expense groups %>
|
|
<%# Local variables: groups, total, type (:income or :expense), amount_sort_params, current_sort_by, current_sort_direction %>
|
|
|
|
<%
|
|
color_class = type == :income ? "text-success" : "text-destructive"
|
|
icon_name = type == :income ? "trending-up" : "trending-down"
|
|
title_key = type == :income ? "reports.transactions_breakdown.table.income" : "reports.transactions_breakdown.table.expense"
|
|
%>
|
|
|
|
<div>
|
|
<h3 class="text-base font-semibold <%= color_class %> mb-4 flex items-center gap-2">
|
|
<%= icon(icon_name, class: "w-5 h-5") %>
|
|
<%= t(title_key) %>
|
|
<span class="text-sm font-normal text-tertiary">(<%= Money.new(total, Current.family.currency).format %>)</span>
|
|
</h3>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-tertiary">
|
|
<th class="text-left py-3 pr-4 font-medium text-secondary"><%= t("reports.transactions_breakdown.table.category") %></th>
|
|
<th class="text-right py-3 px-4 font-medium text-secondary">
|
|
<%= link_to reports_path(amount_sort_params), class: "inline-flex items-center gap-1 hover:text-primary" do %>
|
|
<%= t("reports.transactions_breakdown.table.amount") %>
|
|
<% if current_sort_by == "amount" %>
|
|
<%= icon(current_sort_direction == "desc" ? "chevron-down" : "chevron-up", class: "w-3 h-3") %>
|
|
<% end %>
|
|
<% end %>
|
|
</th>
|
|
<th class="text-right py-3 pl-4 font-medium text-secondary"><%= t("reports.transactions_breakdown.table.percentage") %></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% groups.each do |group| %>
|
|
<% percentage = total.zero? ? 0 : (group[:total].to_f / total * 100).round(1) %>
|
|
<% has_subcategories = group[:subcategories].present? && group[:subcategories].any? %>
|
|
<tr class="border-b border-tertiary hover:bg-surface-inset">
|
|
<td class="py-3 pr-4">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-3 h-3 rounded-full flex-shrink-0" style="background-color: <%= group[:category_color] %>"></span>
|
|
<span class="font-medium text-primary"><%= group[:category_name] %></span>
|
|
<span class="text-xs text-tertiary whitespace-nowrap">(<%= t("reports.transactions_breakdown.table.entries", count: group[:count]) %>)</span>
|
|
</div>
|
|
</td>
|
|
<td class="py-3 px-4 text-right">
|
|
<span class="font-semibold <%= color_class %>">
|
|
<%= Money.new(group[:total], Current.family.currency).format %>
|
|
</span>
|
|
</td>
|
|
<td class="py-3 pl-4 text-right">
|
|
<span class="text-sm text-secondary">
|
|
<%= percentage %>%
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<%# Render subcategories if present %>
|
|
<% if has_subcategories %>
|
|
<% group[:subcategories].each do |subcategory| %>
|
|
<% sub_percentage = total.zero? ? 0 : (subcategory[:total].to_f / total * 100).round(1) %>
|
|
<tr class="border-b border-tertiary hover:bg-surface-inset bg-surface-inset/30">
|
|
<td class="py-2 pr-4 pl-6">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 rounded-full flex-shrink-0" style="background-color: <%= subcategory[:category_color] %>"></span>
|
|
<span class="text-sm text-secondary"><%= subcategory[:category_name] %></span>
|
|
<span class="text-xs text-tertiary whitespace-nowrap">(<%= t("reports.transactions_breakdown.table.entries", count: subcategory[:count]) %>)</span>
|
|
</div>
|
|
</td>
|
|
<td class="py-2 px-4 text-right">
|
|
<span class="text-sm <%= color_class %>">
|
|
<%= Money.new(subcategory[:total], Current.family.currency).format %>
|
|
</span>
|
|
</td>
|
|
<td class="py-2 pl-4 text-right">
|
|
<span class="text-xs text-tertiary">
|
|
<%= sub_percentage %>%
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<% end %>
|
|
<% end %>
|
|
<% end %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|