Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4a39f65
feat: Add toggle on mobile to show/hide checkboxes in transaction page
alessiocappa Dec 13, 2025
2cfd431
fix: Add multi-select toggle also in activities page. Make JS control…
alessiocappa Dec 13, 2025
36bec8e
feat: Add category in mobile view
alessiocappa Dec 13, 2025
48a2e47
feat: Add mobile layout for transaction categories
alessiocappa Dec 13, 2025
b170d4f
feat: Add margin for pagination on mobile
alessiocappa Dec 13, 2025
804ff1f
fix: Ensure category exists when displaying the name
alessiocappa Dec 13, 2025
5dfd172
fix: Adjust mobile paddings
alessiocappa Dec 13, 2025
025afc0
fix: Display "uncategorized" label if no category is set
alessiocappa Dec 13, 2025
b794ef8
fix: Expand transaction name/subtitle
alessiocappa Dec 13, 2025
82c28b1
feat: Add merchant name on desktop view
alessiocappa Dec 13, 2025
64c2871
feat: Move merchant name before account name
alessiocappa Dec 13, 2025
bdcf985
fix: Add class to hide merchant on mobile
alessiocappa Dec 13, 2025
8eebe9d
feat: Add merchant logo on mobile
alessiocappa Dec 13, 2025
22a444d
fix: add pointer-events-none to merchant image on mobile view
alessiocappa Dec 13, 2025
7fa99cd
feat: toggle header checkbox in transaction page when button is clicked
alessiocappa Dec 13, 2025
ad90345
Remove unnecessary CSS class
alessiocappa Dec 13, 2025
11b451c
Remove duplicate CSS class
alessiocappa Dec 13, 2025
47ced2f
Remove wrong Enable Banking logo URL
alessiocappa Dec 13, 2025
9766c50
Update app/views/transactions/_transaction.html.erb
alessiocappa Dec 13, 2025
de36292
Revert "Update app/views/transactions/_transaction.html.erb"
alessiocappa Dec 13, 2025
b006d21
Add translation for Loan Payment/Transfer
alessiocappa Dec 13, 2025
ba549c9
Apply review comments
alessiocappa Dec 13, 2025
de24ced
Add accessible name for toggle based on review comments
alessiocappa Dec 13, 2025
7b61e2f
Use border instead of border-1 class
alessiocappa Dec 13, 2025
9f54836
Apply review comments
alessiocappa Dec 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/components/UI/account/activity_date.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-secondary">
<div class="flex pl-0.5 items-center gap-4">
<%= check_box_tag "#{date}_entries_selection",
class: ["checkbox checkbox--light", "hidden": entries.size == 0],
class: ["checkbox checkbox--light hidden lg:block", "lg:hidden": entries.size == 0],
id: "selection_entry_#{date}",
data: { action: "bulk-select#toggleGroupSelection" } %>
data: {
action: "bulk-select#toggleGroupSelection",
checkbox_toggle_target: "selectionEntry"
} %>

<p class="uppercase space-x-1.5">
<%= tag.span I18n.l(date, format: :long) %>
Expand Down
21 changes: 17 additions & 4 deletions app/components/UI/account/activity_feed.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<%= turbo_frame_tag dom_id(account, "entries") do %>
<div class="bg-container p-5 shadow-border-xs rounded-xl">
<div class="bg-container px-3 py-4 lg:p-4 shadow-border-xs rounded-xl" data-controller="checkbox-toggle">
<div class="flex items-center justify-between mb-4" data-testid="activity-menu">
<%= tag.h2 t("accounts.show.activity.title"), class: "font-medium text-lg" %>

Expand Down Expand Up @@ -46,8 +46,18 @@
"data-auto-submit-form-target": "auto" %>
</div>
</div>
</div>
<% end %>
<%= button_tag type: "button",
id: "toggle-checkboxes-button",
aria: { label: t(".toggle_selection_checkboxes") },
class: "lg:hidden font-medium whitespace-nowrap inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm text-primary border border-secondary hover:bg-surface-hover",
data: {
action: "click->checkbox-toggle#toggle",
checkbox_toggle_target: "toggleButton"
} do %>
<%= helpers.icon("list-todo") %>
<% end %>
</div>
</div>

<% if activity_dates.empty? %>
Expand All @@ -66,8 +76,11 @@
<div class="grid bg-container-inset rounded-xl grid-cols-12 items-center uppercase text-xs font-medium text-secondary px-5 py-3 mb-4">
<div class="pl-0.5 col-span-8 flex items-center gap-4">
<%= check_box_tag "selection_entry",
class: "checkbox checkbox--light",
data: { action: "bulk-select#togglePageSelection" } %>
class: "checkbox checkbox--light hidden lg:block",
data: {
action: "bulk-select#togglePageSelection",
checkbox_toggle_target: "selectionEntry"
} %>
<%= tag.p t("accounts.show.activity.date") %>
</div>

Expand Down
5 changes: 5 additions & 0 deletions app/controllers/transaction_categories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def update
partial: "categories/menu",
locals: { transaction: transaction }
),
turbo_stream.replace(
"category_name_mobile_#{transaction.id}",
partial: "categories/category_name_mobile",
locals: { transaction: transaction }
),
*flash_notification_stream_items
]
end
Expand Down
39 changes: 39 additions & 0 deletions app/javascript/controllers/checkbox_toggle_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["selectionEntry", "toggleButton"]

toggle() {
if (this.selectionEntryTargets.length === 0) return

const shouldShow = this.selectionEntryTargets[0].classList.contains("hidden")

this.selectionEntryTargets.forEach((el) => {
if (shouldShow) {
el.classList.remove("hidden")
} else {
el.classList.add("hidden")
}
})

if (!shouldShow) {
const bulkSelectElement =
this.element.querySelector("[data-controller~='bulk-select']") ||
this.element.closest("[data-controller~='bulk-select']") ||
document.querySelector("[data-controller~='bulk-select']")
if (bulkSelectElement) {
const bulkSelectController = this.application.getControllerForElementAndIdentifier(
bulkSelectElement,
"bulk-select"
)
if (bulkSelectController) {
bulkSelectController.deselectAll()
}
}
}

if (this.hasToggleButtonTarget) {
this.toggleButtonTarget.classList.toggle("bg-surface", shouldShow)
}
}
}
11 changes: 7 additions & 4 deletions app/views/accounts/show/_activity.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<%# locals: (account:) %>

<%= turbo_frame_tag dom_id(account, "entries") do %>
<div class="bg-container p-5 shadow-border-xs rounded-xl">
<div class="bg-container px-3 py-4 lg:p-4 shadow-border-xs rounded-xl">
<div class="flex items-center justify-between mb-4" data-testid="activity-menu">
<%= tag.h2 t(".title"), class: "font-medium text-lg" %>
<% unless @account.linked? %>
Expand Down Expand Up @@ -55,7 +55,7 @@
<% else %>
<%= tag.div id: dom_id(@account, "entries_bulk_select"),
data: {
controller: "bulk-select",
controller: "bulk-select checkbox-toggle",
bulk_select_singular_label_value: t(".entry"),
bulk_select_plural_label_value: t(".entries")
} do %>
Expand All @@ -66,8 +66,11 @@
<div class="grid bg-container-inset rounded-xl grid-cols-12 items-center uppercase text-xs font-medium text-secondary px-5 py-3 mb-4">
<div class="pl-0.5 col-span-8 flex items-center gap-4">
<%= check_box_tag "selection_entry",
class: "checkbox checkbox--light",
data: { action: "bulk-select#togglePageSelection" } %>
class: "checkbox checkbox--light hidden lg:block",
data: {
action: "bulk-select#togglePageSelection",
checkbox_toggle_target: "selectionEntry"
} %>
<p><%= t(".date") %></p>
</div>
<%= tag.p t(".amount"), class: "col-span-2 justify-self-end" %>
Expand Down
14 changes: 14 additions & 0 deletions app/views/categories/_badge_mobile.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%# locals: (category:) %>
<% category ||= Category.uncategorized %>

<div>
<span class="flex lg:hidden items-center gap-1 text-sm font-medium rounded-full px-1.5 py-1 border truncate focus-visible:outline-none focus-visible:ring-0 w-8 h-8 justify-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.lucide_icon.present? %>
<%= icon category.lucide_icon, size: "sm", color: "current" %>
<% end %>
</span>
</div>
7 changes: 7 additions & 0 deletions app/views/categories/_category_name_mobile.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<span id="category_name_mobile_<%= transaction.id %>" class="text-secondary lg:hidden">
<% if transaction.transfer&.categorizable? || transaction.transfer.nil? %>
<%= transaction.category&.name || Category.uncategorized.name %>
<% else %>
<%= transaction.transfer&.payment? ? payment_category.name : transfer_category.name %>
<% end %>
</span>
7 changes: 6 additions & 1 deletion app/views/categories/_menu.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

<%= render DS::Menu.new(variant: "button") do |menu| %>
<% menu.with_button do %>
<% render partial: "categories/badge", locals: { category: transaction.category } %>
<div class="hidden lg:flex">
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
</div>
<div class="flex lg:hidden">
<%= render partial: "categories/badge_mobile", locals: { category: transaction.category } %>
</div>
<% end %>

<% menu.with_custom_content do %>
Expand Down
1 change: 1 addition & 0 deletions app/views/category/dropdowns/_row.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
}
),
method: :patch,
data: { turbo_frame: "category_dropdown" },
class: "flex w-full items-center gap-1.5 cursor-pointer focus:outline-none" do %>

<%= icon("check") if is_selected %>
Expand Down
7 changes: 5 additions & 2 deletions app/views/entries/_entry_group.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
<div class="py-2 px-4 flex items-center justify-between font-medium text-xs text-secondary">
<div class="flex pl-0.5 items-center gap-4">
<%= check_box_tag "#{date}_entries_selection",
class: ["checkbox checkbox--light", "hidden": entries.size == 0],
class: ["checkbox checkbox--light hidden lg:block", "lg:hidden": entries.size == 0],
id: "selection_entry_#{date}",
data: { action: "bulk-select#toggleGroupSelection" } %>
data: {
action: "bulk-select#toggleGroupSelection",
checkbox_toggle_target: "selectionEntry"
} %>

<p class="uppercase space-x-1.5">
<%= tag.span I18n.l(date, format: :long) %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/entries/_selection_bar.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="fixed bottom-6 z-10 flex items-center justify-between rounded-xl bg-container-inset border-primary shadow-border-xs px-4 text-sm text-primary w-[420px] py-1.5">
<div class="fixed bottom-30 md:bottom-6 z-10 flex items-center justify-between rounded-xl bg-container-inset border-primary shadow-border-xs px-4 text-sm text-primary md:w-[420px] w-[90%] py-1.5">
<div class="flex items-center gap-2">
<%= check_box_tag "entry_selection", 1, true, class: "checkbox checkbox--light", data: { action: "bulk-select#deselectAll" } %>

Expand Down
2 changes: 1 addition & 1 deletion app/views/shared/_pagination.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<%# locals: (pagy:) %>

<nav class="flex w-full items-center justify-between">
<div class="flex items-center gap-1">
<div class="flex items-center gap-1 mr-3">
<div>
<% if pagy.prev %>
<%= link_to pagy_url_for(pagy, pagy.prev),
Expand Down
2 changes: 1 addition & 1 deletion app/views/shared/_ruler.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<%# locals: (classes: nil) %>
<hr class="border-divider <%= classes || "mx-4" %>">
<hr class="border-divider <%= classes || "mx-3 lg:mx-4" %>">
72 changes: 48 additions & 24 deletions app/views/transactions/_transaction.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,45 @@

<%= turbo_frame_tag dom_id(entry) do %>
<%= turbo_frame_tag dom_id(transaction) do %>
<div class="grid grid-cols-12 items-center text-primary text-sm font-medium p-4 lg:p-4 <%= entry.excluded ? "opacity-50 text-gray-400" : "" %>">
<div class="flex lg:grid lg:grid-cols-12 items-center text-primary text-sm font-medium p-3 lg:p-4 <%= entry.excluded ? "opacity-50 text-gray-400" : "" %>">

<div class="pr-4 lg:pr-10 flex items-center gap-3 lg:gap-4 col-span-8">
<div class="pr-4 lg:pr-10 flex items-center gap-3 lg:gap-4 col-span-8 min-w-0">
<%= check_box_tag dom_id(entry, "selection"),
disabled: transaction.transfer.present?,
class: "checkbox checkbox--light",
class: "checkbox checkbox--light hidden lg:block",
data: {
id: entry.id,
"bulk-select-target": "row",
action: "bulk-select#toggleRowSelection"
action: "bulk-select#toggleRowSelection",
checkbox_toggle_target: "selectionEntry"
} %>

<div class="max-w-full">
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
<% if transaction.merchant&.logo_url.present? %>
<%= image_tag transaction.merchant.logo_url,
class: "w-6 h-6 rounded-full",
loading: "lazy" %>
<% else %>
<%= render DS::FilledIcon.new(
variant: :text,
text: entry.name,
size: "sm",
rounded: true
) %>
<% end %>
<%= content_tag :div, class: ["flex items-center gap-3 lg:gap-4"] do %>
<div class="hidden lg:flex">
<% if transaction.merchant&.logo_url.present? %>
<%= image_tag transaction.merchant.logo_url,
class: "w-9 h-9 rounded-full",
loading: "lazy" %>
<% else %>
<div class="hidden lg:flex">
<%= render DS::FilledIcon.new(
variant: :text,
text: entry.name,
size: "lg",
rounded: true
) %>
</div>
<% end %>
</div>
<div class="flex md:hidden items-center gap-1 col-span-2 relative">
<%= render "transactions/transaction_category", transaction: transaction %>
<% if transaction.merchant&.logo_url.present? %>
<%= image_tag transaction.merchant.logo_url,
class: "w-5 h-5 rounded-full absolute -bottom-1 -right-1 border border-secondary pointer-events-none",
loading: "lazy" %>
<% end %>
</div>

<div class="truncate">
<div class="space-y-0.5">
Expand Down Expand Up @@ -71,16 +84,27 @@
</div>
</div>

<div class="text-secondary text-xs font-normal hidden lg:block">
<div class="text-secondary text-xs font-normal">
<% if transaction.transfer? %>
<span class="text-secondary">
<%= transaction.loan_payment? ? "Loan Payment" : "Transfer" %> • <%= entry.account.name %>
<%= transaction.loan_payment? ? t("transactions.show.loan_payment") : t("transactions.show.transfer") %> • <%= entry.account.name %>
</span>
<% else %>
<%= link_to entry.account.name,
account_path(entry.account, tab: "transactions"),
data: { turbo_frame: "_top" },
class: "hover:underline" %>
<% if transaction.merchant&.present? %>
<span class="hidden lg:inline truncate"><%= transaction.merchant.name %> • </span>
<% end %>
<span class="text-secondary hidden lg:inline">
<%= link_to entry.account.name,
account_path(entry.account, tab: "transactions"),
data: { turbo_frame: "_top" },
class: "hover:underline" %>
</span>
<div class="flex items-center gap-1 truncate">
<%= render "categories/category_name_mobile", transaction: transaction %>
<% if transaction.merchant&.present? %>
<span class="lg:hidden truncate">• <%= transaction.merchant.name %></span>
<% end %>
</div>
<% end %>
</div>
</div>
Expand All @@ -93,7 +117,7 @@
<%= render "transactions/transaction_category", transaction: transaction %>
</div>

<div class="col-span-2 col-start-11 md:col-start-auto ml-auto text-right">
<div class="shrink-0 col-span-4 lg:col-span-2 ml-auto text-right">
<%= content_tag :p,
transaction.transfer? && view_ctx == "global" ? "+/- #{format_money(entry.amount_money.abs)}" : format_money(-entry.amount_money),
class: ["text-green-600": entry.amount.negative?] %>
Expand Down
7 changes: 6 additions & 1 deletion app/views/transactions/_transaction_category.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<% if transaction.transfer&.categorizable? || transaction.transfer.nil? %>
<%= render "categories/menu", transaction: transaction %>
<% else %>
<%= render "categories/badge", category: transaction.transfer&.payment? ? payment_category : transfer_category %>
<div class="hidden lg:flex">
<%= render "categories/badge", category: transaction.transfer&.payment? ? payment_category : transfer_category %>
</div>
<div class="flex lg:hidden">
<%= render "categories/badge_mobile", category: transaction.transfer&.payment? ? payment_category : transfer_category %>
</div>
<% end %>
</div>
5 changes: 4 additions & 1 deletion app/views/transactions/_transfer_match.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
<%= icon "link-2", size: "sm", class: "text-secondary" %>
</span>
<% elsif transaction.transfer.pending? %>
<span class="inline-flex items-center rounded-full bg-surface-inset px-2 py-0.5 text-xs font-medium text-secondary">
<span class="hidden lg:inline-flex items-center rounded-full bg-surface-inset px-2 py-0.5 text-xs font-medium text-secondary">
Auto-matched
</span>
<span class="inline-flex lg:hidden items-center rounded-full bg-surface-inset px-2 py-0.5 text-xs font-medium text-secondary">
A/M
</span>

<%= button_to transfer_path(transaction.transfer, transfer: { status: "confirmed" }),
method: :patch,
Expand Down
11 changes: 7 additions & 4 deletions app/views/transactions/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@
<%= render "summary", totals: @search.totals %>

<div id="transactions"
data-controller="bulk-select"
data-controller="bulk-select checkbox-toggle"
data-bulk-select-singular-label-value="<%= t(".transaction") %>"
data-bulk-select-plural-label-value="<%= t(".transactions") %>"
class="flex flex-col bg-container rounded-xl shadow-border-xs p-4">
class="flex flex-col bg-container rounded-xl shadow-border-xs px-3 py-4 lg:p-4">
<%= render "transactions/searches/search" %>

<div id="entry-selection-bar" data-bulk-select-target="selectionBar" class="flex justify-center hidden">
Expand All @@ -62,8 +62,11 @@
<div class="grid-cols-12 bg-container-inset rounded-xl px-5 py-3 text-xs uppercase font-medium text-secondary items-center mb-4 grid">
<div class="pl-0.5 col-span-8 flex items-center gap-4">
<%= check_box_tag "selection_entry",
class: "checkbox checkbox--light",
data: { action: "bulk-select#togglePageSelection" } %>
class: "checkbox checkbox--light hidden lg:block",
data: {
action: "bulk-select#togglePageSelection",
checkbox_toggle_target: "selectionEntry"
} %>
<p>transaction</p>
</div>

Expand Down
11 changes: 11 additions & 0 deletions app/views/transactions/searches/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,16 @@
<%= render "transactions/searches/menu", form: form %>
<% end %>
<% end %>

<%= button_tag type: "button",
id: "toggle-checkboxes-button",
aria: { label: t(".toggle_selection_checkboxes") },
class: "lg:hidden font-medium whitespace-nowrap inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm text-primary border border-secondary hover:bg-surface-hover",
data: {
action: "click->checkbox-toggle#toggle",
checkbox_toggle_target: "toggleButton"
} do %>
<%= icon("list-todo") %>
<% end %>
Comment on lines +34 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg "toggle_selection_checkboxes" config/locales/ -A 2 -B 2

Repository: we-promise/sure

Length of output: 41


🏁 Script executed:

cat -n app/views/transactions/searches/_form.html.erb | sed -n '34,43p'

Repository: we-promise/sure

Length of output: 618


🏁 Script executed:

# Check for any related i18n keys in the transactions search form locale
rg "transactions\.searches\.form\." config/locales/ | head -20

Repository: we-promise/sure

Length of output: 41


Add the missing i18n key to your locale files.

The button uses t(".toggle_selection_checkboxes") but the translation key transactions.searches.form.toggle_selection_checkboxes does not exist in config/locales/. Without this key, the aria-label will not render properly and the application may display the translation key as fallback text.

Add the key to your locale files:

# config/locales/en.yml (or appropriate locale)
en:
  transactions:
    searches:
      form:
        toggle_selection_checkboxes: "Toggle all checkboxes"  # or appropriate label text

The rest of the implementation (Stimulus bindings, icon helper, Tailwind tokens, semantic HTML) is correct.

🤖 Prompt for AI Agents
In app/views/transactions/searches/_form.html.erb around lines 34 to 43, the
button calls t(".toggle_selection_checkboxes") but the i18n key
transactions.searches.form.toggle_selection_checkboxes is missing from
config/locales, causing the aria-label fallback to display the key; add the
missing key to your locale YAML (e.g., en) under transactions -> searches ->
form with an appropriate string like "Toggle all checkboxes" so the translation
resolves correctly.

</div>
<% end %>
Loading