mirror of
https://github.com/we-promise/sure.git
synced 2026-04-10 07:44:48 +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>
88 lines
3.4 KiB
Plaintext
88 lines
3.4 KiB
Plaintext
<div>
|
|
<%# Export Controls %>
|
|
<div class="flex items-center justify-end mb-4 flex-wrap gap-3">
|
|
<%
|
|
# Build params hash for links
|
|
base_params = {
|
|
period_type: period_type,
|
|
start_date: start_date,
|
|
end_date: end_date,
|
|
sort_by: params[:sort_by],
|
|
sort_direction: params[:sort_direction]
|
|
}.compact
|
|
%>
|
|
|
|
<%# Export Options %>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-sm text-secondary"><%= t("reports.transactions_breakdown.export.label") %>:</span>
|
|
<%= link_to export_transactions_reports_path(base_params.merge(format: :csv)),
|
|
class: "inline-flex items-center gap-1 text-sm px-3 py-1 bg-surface-inset text-secondary hover:bg-surface-hover rounded-lg" do %>
|
|
<%= icon("download", class: "w-3 h-3") %>
|
|
<span><%= t("reports.transactions_breakdown.export.csv") %></span>
|
|
<% end %>
|
|
<%= link_to google_sheets_instructions_reports_path(base_params),
|
|
class: "inline-flex items-center gap-1 text-sm px-3 py-1 bg-surface-inset text-secondary hover:bg-surface-hover rounded-lg",
|
|
data: { turbo_frame: "modal" } do %>
|
|
<%= icon("external-link", class: "w-3 h-3") %>
|
|
<span><%= t("reports.transactions_breakdown.export.google_sheets") %></span>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
|
|
<%# Transactions Tables - Split by Income and Expenses %>
|
|
<% if transactions.any? %>
|
|
<%
|
|
# Separate income and expenses
|
|
income_groups = transactions.select { |g| g[:type] == "income" }
|
|
expense_groups = transactions.select { |g| g[:type] == "expense" }
|
|
|
|
# Calculate totals
|
|
income_total = income_groups.sum { |g| g[:total] }
|
|
expense_total = expense_groups.sum { |g| g[:total] }
|
|
|
|
# Determine sort direction for Amount column
|
|
current_sort_by = params[:sort_by]
|
|
current_sort_direction = params[:sort_direction]
|
|
|
|
# Toggle sort direction: if currently sorting by amount desc, switch to asc; otherwise default to desc
|
|
next_sort_direction = (current_sort_by == "amount" && current_sort_direction == "desc") ? "asc" : "desc"
|
|
|
|
# Build params for amount sort link
|
|
amount_sort_params = base_params.merge(sort_by: "amount", sort_direction: next_sort_direction)
|
|
%>
|
|
|
|
<div class="space-y-8">
|
|
<%# Income Section %>
|
|
<% if income_groups.any? %>
|
|
<%= render "reports/breakdown_table",
|
|
groups: income_groups,
|
|
total: income_total,
|
|
type: :income,
|
|
amount_sort_params: amount_sort_params,
|
|
current_sort_by: current_sort_by,
|
|
current_sort_direction: current_sort_direction %>
|
|
<% end %>
|
|
|
|
<%# Expenses Section %>
|
|
<% if expense_groups.any? %>
|
|
<%= render "reports/breakdown_table",
|
|
groups: expense_groups,
|
|
total: expense_total,
|
|
type: :expense,
|
|
amount_sort_params: amount_sort_params,
|
|
current_sort_by: current_sort_by,
|
|
current_sort_direction: current_sort_direction %>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%# Summary Stats %>
|
|
<div class="mt-4 text-sm text-secondary">
|
|
<%= t("reports.transactions_breakdown.pagination.showing", count: transactions.sum { |g| g[:count] }) %>
|
|
</div>
|
|
<% else %>
|
|
<div class="text-center py-8 text-tertiary">
|
|
<%= t("reports.transactions_breakdown.no_transactions") %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|