mirror of
https://github.com/we-promise/sure.git
synced 2026-08-05 08:32:15 +00:00
* feat(ds): elevate dropdown overlays and stabilize selection check gutter Menus and popovers floated at the same elevation as inline cards (shadow-border-xs), so dropdowns blended into the content beneath them. Bump DS::Menu and DS::Popover panels to shadow-border-lg. DS::MenuItem rendered its leading icon only when present, so a selection check shifted the row's text out of alignment with the unselected rows. Add a `selected:` param that reserves a fixed-width check gutter (check when selected, empty otherwise) so row text stays aligned. Apply the same reserved gutter to the bespoke category dropdown row, and add a `selectable` menu preview. * feat(dashboard): unify per-widget period selectors into one picker The dashboard rendered three identical period <select>s (cashflow, outflows, net worth), each writing the same global User#default_period and full-reloading the page via turbo_frame "_top" — so changing one changed all. Replace them with a single shared UI::PeriodPicker (DS::Menu of period links) in a toolbar, and wrap the sections grid in a "dashboard_sections" Turbo frame so a period change swaps only the dashboard (no full-page reload). Reuse the same picker on the account chart, removing its duplicate select. * refactor(dashboard): use DS::MenuItem selected: gutter in period picker Now that DS::MenuItem reserves a check gutter, the period picker passes selected: instead of a conditional leading check icon, so the current period's row stays aligned with the rest. * fix(ds): expose menu selection via menuitemradio + aria-checked Selectable DS::MenuItem rows conveyed selection only visually. Render them as role="menuitemradio" with aria-checked so assistive tech gets the selection state of single-select lists, merging the menu ARIA contract with any caller-supplied aria. Addresses CodeRabbit review feedback. * refactor(dashboard): drop redundant aria-current from period picker DS::MenuItem now exposes selection via menuitemradio + aria-checked, so the period picker no longer needs its own aria-current. Update the component test to assert the new ARIA. * fix(ds): include selectable roles in menu roving-focus query DS::MenuItem selectable rows render as role=menuitemradio, but the menu controller built its roving-focus list from [role=menuitem] only, leaving single-select menus with no keyboard focus/arrow handling. Query the menuitemradio/menuitemcheckbox roles too. Addresses Codex review feedback. * fix(dashboard): keep non-picker links out of frame + fix custom-month picker - turbo_frame_tag "dashboard_sections" now targets _top so ordinary links inside the sections (e.g. Balance Sheet account links) navigate the page instead of failing to resolve inside the frame. - Period.current_month_for / last_month_for carry their semantic key for custom-month families, so the picker shows the right label and checks the right option instead of falling back to 30D. Addresses Codex review feedback. * fix(dashboard): announce selected period in picker trigger's accessible name The static "Select time period" aria-label overrode the visible selected label as the trigger's accessible name, so assistive tech kept announcing the same name regardless of selection. Interpolate the selected short label into the aria-label and pin it with a component test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
119 lines
6.0 KiB
Plaintext
119 lines
6.0 KiB
Plaintext
<%# locals: (outflows_data:, period:) %>
|
|
<div id="outflows-donut-section" class="@container">
|
|
<div class="px-4">
|
|
<div class="flex flex-col @2xl:flex-row gap-8 items-center">
|
|
<!-- Donut Chart -->
|
|
<div class="w-full @2xl:w-1/3 max-w-[300px] px-4">
|
|
<div class="h-[300px] relative"
|
|
data-controller="donut-chart"
|
|
data-donut-chart-segments-value="<%= outflows_data[:categories].to_json %>"
|
|
data-donut-chart-segment-opacity-value="0.9"
|
|
data-donut-chart-extended-hover-value="true"
|
|
data-donut-chart-hover-extension-value="3"
|
|
data-donut-chart-enable-click-value="true"
|
|
data-donut-chart-start-date-value="<%= period.date_range.first %>"
|
|
data-donut-chart-end-date-value="<%= period.date_range.last %>">
|
|
<div data-donut-chart-target="chartContainer" class="absolute inset-0 pointer-events-none"></div>
|
|
|
|
<div data-donut-chart-target="contentContainer" class="flex justify-center items-center h-full">
|
|
<div data-donut-chart-target="defaultContent" class="flex flex-col items-center">
|
|
<div class="text-secondary text-sm mb-2">
|
|
<span><%= t("pages.dashboard.outflows_donut.total_outflows") %></span>
|
|
</div>
|
|
|
|
<div data-donut-chart-target="amount" class="text-3xl font-medium text-primary privacy-sensitive whitespace-nowrap">
|
|
<%= format_money Money.new(outflows_data[:total], outflows_data[:currency]) %>
|
|
</div>
|
|
</div>
|
|
|
|
<% outflows_data[:categories].each do |category| %>
|
|
<div id="segment_<%= category[:id] %>" class="hidden">
|
|
<div class="flex flex-col gap-2 items-center">
|
|
<p class="text-sm text-secondary"><%= category[:name] %></p>
|
|
|
|
<p data-donut-chart-target="amount" class="text-3xl font-medium text-primary privacy-sensitive whitespace-nowrap">
|
|
<%= outflows_data[:currency_symbol] %><%= number_with_delimiter(category[:amount], delimiter: ",") %>
|
|
</p>
|
|
|
|
<p class="text-sm text-secondary privacy-sensitive"><%= category[:percentage] %>%</p>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category List -->
|
|
<div class="w-full @2xl:w-2/3 bg-container-inset rounded-xl p-1 space-y-1 overflow-x-auto">
|
|
<div class="px-4 py-2 flex items-center uppercase text-xs font-medium text-secondary">
|
|
<div class="w-16 @2xl:w-40">
|
|
<p><%= t("pages.dashboard.outflows_donut.categories") %><span class="text-subdued mx-1">·</span><%= outflows_data[:categories].count %></p>
|
|
</div>
|
|
<div class="ml-auto text-right flex items-center gap-6">
|
|
<div class="w-16 @2xl:w-40">
|
|
<p><%= t("pages.dashboard.outflows_donut.value") %></p>
|
|
</div>
|
|
<div class="w-16">
|
|
<p><%= t("pages.dashboard.outflows_donut.weight") %></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="py-3 shadow-border-xs rounded-lg bg-container font-medium text-sm max-w-full">
|
|
<% outflows_data[:categories].each_with_index do |category, idx| %>
|
|
<%
|
|
category_content = capture do
|
|
%>
|
|
<div class="flex items-center gap-3 flex-1 min-w-0">
|
|
<div class="h-7 w-7 flex-shrink-0 group-hover:scale-105 transition-all duration-300 rounded-full flex justify-center items-center"
|
|
style="
|
|
background-color: color-mix(in oklab, <%= category[:color] %> 10%, transparent);
|
|
border-color: color-mix(in oklab, <%= category[:color] %> 10%, transparent);
|
|
color: <%= category[:color] %>;">
|
|
<% if category[:icon] %>
|
|
<%= icon(category[:icon], color: "current", size: "sm") %>
|
|
<% else %>
|
|
<%= render DS::FilledIcon.new(
|
|
variant: :text,
|
|
hex_color: category[:color],
|
|
text: category[:name],
|
|
size: "sm",
|
|
rounded: true
|
|
) %>
|
|
<% end %>
|
|
</div>
|
|
<span class="text-sm font-medium text-primary truncate"><%= category[:name] %></span>
|
|
</div>
|
|
<div class="flex items-center gap-4 flex-shrink-0 text-right">
|
|
<span class="text-sm font-medium text-primary whitespace-nowrap privacy-sensitive"><%= format_money Money.new(category[:amount], category[:currency]) %></span>
|
|
<span class="text-sm text-secondary whitespace-nowrap w-10 @2xl:w-15 privacy-sensitive"><%= category[:percentage] %>%</span>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% if category[:clickable] != false %>
|
|
<%= link_to transactions_path(q: { categories: [category[:name]], start_date: period.date_range.first, end_date: period.date_range.last }),
|
|
class: "flex items-center justify-between mx-3 p-3 rounded-lg cursor-pointer group gap-3",
|
|
data: {
|
|
turbo_frame: "_top",
|
|
turbo_prefetch: false,
|
|
category_id: category[:id],
|
|
action: "mouseenter->donut-chart#highlightSegment mouseleave->donut-chart#unhighlightSegment"
|
|
} do %>
|
|
<%= category_content %>
|
|
<% end %>
|
|
<% else %>
|
|
<div class="flex items-center justify-between mx-3 p-3 rounded-lg group gap-3"
|
|
data-category-id="<%= category[:id] %>"
|
|
data-action="mouseenter->donut-chart#highlightSegment mouseleave->donut-chart#unhighlightSegment">
|
|
<%= category_content %>
|
|
</div>
|
|
<% end %>
|
|
<% if idx < outflows_data[:categories].size - 1 %>
|
|
<%= render "shared/ruler", classes: "mx-3 @2xl:mx-4" %>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|