mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 15:35:22 +02:00
Replace all menus with new ViewComponent
This commit is contained in:
parent
641f32d0d8
commit
2bfd00f611
21 changed files with 209 additions and 243 deletions
|
@ -1,6 +1,6 @@
|
|||
<div data-controller="menu" data-menu-placement-value="<%= @placement %>" data-menu-offset-value="<%= @offset %>">
|
||||
<% if @variant == :icon %>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "more-horizontal", data: { menu_target: "button" }) %>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: @icon_vertical ? "more-vertical" : "more-horizontal", data: { menu_target: "button" }) %>
|
||||
<% elsif @variant == :button %>
|
||||
<%= button %>
|
||||
<% elsif @variant == :avatar %>
|
||||
|
@ -18,6 +18,8 @@
|
|||
<% items.each do |item| %>
|
||||
<%= item %>
|
||||
<% end %>
|
||||
|
||||
<%= custom_content %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,15 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MenuComponent < ViewComponent::Base
|
||||
renders_one :button, ->(**options) do
|
||||
renders_one :button, ->(**options, &block) do
|
||||
options_with_target = options.merge(data: { menu_target: "button" })
|
||||
ButtonComponent.new(**options_with_target)
|
||||
|
||||
if block
|
||||
content_tag(:button, **options_with_target, &block)
|
||||
else
|
||||
ButtonComponent.new(**options_with_target)
|
||||
end
|
||||
end
|
||||
|
||||
renders_one :header, ->(&block) do
|
||||
content_tag(:div, class: "border-b border-tertiary", &block)
|
||||
end
|
||||
|
||||
renders_one :custom_content
|
||||
|
||||
renders_many :items, MenuItemComponent
|
||||
|
||||
VARIANTS = {
|
||||
|
@ -18,10 +25,11 @@ class MenuComponent < ViewComponent::Base
|
|||
avatar: {}
|
||||
}
|
||||
|
||||
def initialize(variant: "icon", avatar_url: nil, placement: "bottom-end", offset: 12)
|
||||
def initialize(variant: "icon", avatar_url: nil, placement: "bottom-end", offset: 12, icon_vertical: false)
|
||||
@variant = variant.to_sym
|
||||
@avatar_url = avatar_url
|
||||
@placement = placement
|
||||
@offset = offset
|
||||
@icon_vertical = icon_vertical
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,13 +14,14 @@ class MenuItemComponent < ViewComponent::Base
|
|||
<% end %>
|
||||
ERB
|
||||
|
||||
def initialize(variant: "default", text: nil, href: nil, method: :get, icon: nil, data: {})
|
||||
def initialize(variant: "default", text: nil, href: nil, method: :get, icon: nil, destructive: false, data: {})
|
||||
@variant = variant.to_sym
|
||||
@text = text
|
||||
@icon = icon
|
||||
@href = href
|
||||
@method = method.to_sym
|
||||
@data = data
|
||||
@destructive = destructive
|
||||
end
|
||||
|
||||
def wrapper(&block)
|
||||
|
@ -39,7 +40,7 @@ class MenuItemComponent < ViewComponent::Base
|
|||
end
|
||||
|
||||
def destructive?
|
||||
@method == :delete
|
||||
@method == :delete || @destructive
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
module MenusHelper
|
||||
def contextual_menu(icon: "more-horizontal", id: nil, &block)
|
||||
tag.div id: id, data: { controller: "menu" } do
|
||||
concat contextual_menu_icon(icon)
|
||||
concat contextual_menu_content(&block)
|
||||
end
|
||||
end
|
||||
|
||||
def contextual_menu_modal_action_item(label, url, icon: "pencil-line", turbo_frame: :modal, class_name: nil)
|
||||
link_to url, class: "flex items-center rounded-md text-primary hover:bg-container-hover p-2 gap-2 #{class_name}", data: { action: "click->menu#close", turbo_frame: turbo_frame } do
|
||||
concat(lucide_icon(icon, class: "shrink-0 w-5 h-5 text-secondary"))
|
||||
concat(tag.span(label, class: "text-sm"))
|
||||
end
|
||||
end
|
||||
|
||||
def contextual_menu_item(label, url:, icon:, turbo_frame: nil)
|
||||
link_to url, class: "flex items-center rounded-md text-primary hover:bg-container-hover p-2 gap-2", data: { action: "click->menu#close", turbo_frame: turbo_frame } do
|
||||
concat(lucide_icon(icon, class: "shrink-0 w-5 h-5 text-secondary"))
|
||||
concat(tag.span(label, class: "text-sm"))
|
||||
end
|
||||
end
|
||||
|
||||
def contextual_menu_destructive_item(label, url, turbo_confirm: true, turbo_frame: nil)
|
||||
button_to url,
|
||||
method: :delete,
|
||||
class: "flex items-center w-full rounded-md text-red-500 hover:bg-red-500/5 p-2 gap-2",
|
||||
data: { turbo_confirm: turbo_confirm, turbo_frame: } do
|
||||
concat(lucide_icon("trash-2", class: "shrink-0 w-5 h-5"))
|
||||
concat(tag.span(label, class: "text-sm"))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def contextual_menu_icon(icon)
|
||||
tag.button class: "w-9 h-9 flex justify-center items-center hover:bg-surface-hover rounded-lg cursor-pointer focus:outline-none focus-visible:outline-none", data: { menu_target: "button" } do
|
||||
concat lucide_icon("more-vertical", class: "w-5 h-5 text-secondary md:hidden")
|
||||
concat lucide_icon(icon, class: "w-5 h-5 text-secondary hidden md:block")
|
||||
end
|
||||
end
|
||||
|
||||
def contextual_menu_content(&block)
|
||||
tag.div class: "min-w-[200px] p-1 z-50 shadow-border-xs bg-container rounded-lg hidden",
|
||||
data: { menu_target: "content" } do
|
||||
capture(&block)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -45,7 +45,13 @@ export default class extends Controller {
|
|||
// If data is a string, it's the title. Otherwise, return the parsed object.
|
||||
#normalizeRawData(rawData) {
|
||||
try {
|
||||
return JSON.parse(rawData);
|
||||
const parsed = JSON.parse(rawData);
|
||||
|
||||
if (typeof parsed === "boolean") {
|
||||
return { title: "Are you sure?" };
|
||||
}
|
||||
|
||||
return parsed;
|
||||
} catch (e) {
|
||||
return { title: rawData };
|
||||
}
|
||||
|
|
|
@ -1,73 +1,87 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { userPreference: String }
|
||||
static values = { userPreference: String };
|
||||
|
||||
connect() {
|
||||
this.applyTheme()
|
||||
this.startSystemThemeListener()
|
||||
this.applyTheme();
|
||||
this.startSystemThemeListener();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopSystemThemeListener()
|
||||
this.stopSystemThemeListener();
|
||||
}
|
||||
|
||||
// Called automatically by Stimulus when the userPreferenceValue changes (e.g., after form submit/page reload)
|
||||
userPreferenceValueChanged() {
|
||||
this.applyTheme()
|
||||
this.applyTheme();
|
||||
}
|
||||
|
||||
// Called when a theme radio button is clicked
|
||||
updateTheme(event) {
|
||||
const selectedTheme = event.currentTarget.value
|
||||
const selectedTheme = event.currentTarget.value;
|
||||
if (selectedTheme === "system") {
|
||||
this.setTheme(this.systemPrefersDark())
|
||||
this.setTheme(this.systemPrefersDark());
|
||||
} else if (selectedTheme === "dark") {
|
||||
this.setTheme(true)
|
||||
this.setTheme(true);
|
||||
} else {
|
||||
this.setTheme(false)
|
||||
this.setTheme(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Applies theme based on the userPreferenceValue (from server)
|
||||
applyTheme() {
|
||||
if (this.userPreferenceValue === "system") {
|
||||
this.setTheme(this.systemPrefersDark())
|
||||
this.setTheme(this.systemPrefersDark());
|
||||
} else if (this.userPreferenceValue === "dark") {
|
||||
this.setTheme(true)
|
||||
this.setTheme(true);
|
||||
} else {
|
||||
this.setTheme(false)
|
||||
this.setTheme(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets or removes the data-theme attribute
|
||||
setTheme(isDark) {
|
||||
if (isDark) {
|
||||
document.documentElement.setAttribute("data-theme", "dark")
|
||||
document.documentElement.setAttribute("data-theme", "dark");
|
||||
} else {
|
||||
document.documentElement.removeAttribute("data-theme")
|
||||
document.documentElement.removeAttribute("data-theme");
|
||||
}
|
||||
}
|
||||
|
||||
systemPrefersDark() {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
}
|
||||
|
||||
handleSystemThemeChange = (event) => {
|
||||
// Only apply system theme changes if the user preference is currently 'system'
|
||||
if (this.userPreferenceValue === "system") {
|
||||
this.setTheme(event.matches)
|
||||
this.setTheme(event.matches);
|
||||
}
|
||||
};
|
||||
|
||||
toDark() {
|
||||
this.setTheme(true);
|
||||
}
|
||||
|
||||
toLight() {
|
||||
this.setTheme(false);
|
||||
}
|
||||
|
||||
startSystemThemeListener() {
|
||||
this.darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
this.darkMediaQuery.addEventListener("change", this.handleSystemThemeChange)
|
||||
this.darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
this.darkMediaQuery.addEventListener(
|
||||
"change",
|
||||
this.handleSystemThemeChange,
|
||||
);
|
||||
}
|
||||
|
||||
stopSystemThemeListener() {
|
||||
if (this.darkMediaQuery) {
|
||||
this.darkMediaQuery.removeEventListener("change", this.handleSystemThemeChange)
|
||||
this.darkMediaQuery.removeEventListener(
|
||||
"change",
|
||||
this.handleSystemThemeChange,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,15 @@
|
|||
|
||||
<%= render partial: "categories/badge", locals: { category: category } %>
|
||||
</div>
|
||||
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_category_path(category) %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), icon: "pencil", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
|
||||
|
||||
<% if category.transactions.any? %>
|
||||
<%= link_to new_category_deletion_path(category),
|
||||
class: "flex items-center w-full rounded-lg text-red-600 hover:bg-red-50 py-2 px-3 gap-2",
|
||||
data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "trash-2", class: "shrink-0 w-5 h-5" %>
|
||||
<span class="text-sm"><%= t(".delete") %></span>
|
||||
<% end %>
|
||||
<% menu.with_item(text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), data: { turbo_frame: :modal }, method: :delete) %>
|
||||
<% else %>
|
||||
<%= contextual_menu_destructive_item t(".delete"), category_path(category), turbo_confirm: nil %>
|
||||
<% menu.with_item(text: t(".delete"), icon: "trash-2", href: category_path(category), method: :delete) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<%# locals: (transaction:) %>
|
||||
|
||||
<div class="relative" data-controller="menu" id="<%= dom_id(transaction, :category_menu) %>">
|
||||
<button data-menu-target="button" class="flex cursor-pointer">
|
||||
<%= render partial: "categories/badge", locals: { category: transaction.category } %>
|
||||
</button>
|
||||
<div data-menu-target="content" class="absolute z-10 hidden w-screen mt-2 max-w-min cursor-default">
|
||||
<div class="w-80 text-sm font-semibold leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id), loading: :lazy do %>
|
||||
<div class="p-6 flex items-center justify-center">
|
||||
<p class="text-sm text-secondary animate-pulse"><%= t(".loading") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render MenuComponent.new(variant: "button") do |menu| %>
|
||||
<% menu.with_button do %>
|
||||
<% render partial: "categories/badge", locals: { category: transaction.category } %>
|
||||
<% end %>
|
||||
|
||||
<% menu.with_custom_content do %>
|
||||
<%= turbo_frame_tag "category_dropdown", src: category_dropdown_path(category_id: transaction.category_id, transaction_id: transaction.id), loading: :lazy do %>
|
||||
<div class="p-6 flex items-center justify-center">
|
||||
<p class="text-sm text-secondary animate-pulse"><%= t(".loading") %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -24,23 +24,8 @@
|
|||
<%= render partial: "categories/badge", locals: { category: category } %>
|
||||
<% end %>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= link_to edit_category_path(category),
|
||||
class: "block w-full py-2 px-3 space-x-2 text-primary hover:bg-gray-50 flex items-center rounded-lg",
|
||||
data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "pencil-line", class: "w-5 h-5 text-secondary" %>
|
||||
|
||||
<span><%= t(".edit") %></span>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_category_deletion_path(category),
|
||||
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
|
||||
data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "trash-2", class: "w-5 h-5" %>
|
||||
|
||||
<span><%= t(".delete") %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), icon: "pencil-line", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), data: { turbo_frame: :modal }, destructive: true) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -9,8 +9,18 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<%= contextual_menu icon: "more-vertical" do %>
|
||||
<%= contextual_menu_item("Edit chat", url: edit_chat_path(chat), icon: "pencil", turbo_frame: dom_id(chat, :title)) %>
|
||||
<%= contextual_menu_destructive_item("Delete chat", chat_path(chat)) %>
|
||||
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
|
||||
<% menu.with_item(text: "Edit chat", href: edit_chat_path(chat), icon: "pencil", data: { turbo_frame: dom_id(chat, "title") }) %>
|
||||
<% menu.with_item(
|
||||
text: "Delete chat",
|
||||
href: chat_path(chat),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Are you sure you want to delete this chat?",
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -13,12 +13,22 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%= contextual_menu icon: "more-vertical", id: "chat-menu" do %>
|
||||
<%= contextual_menu_item "Start new chat", url: new_chat_path, icon: "plus" %>
|
||||
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
|
||||
<% menu.with_item(text: "Start new chat", href: new_chat_path, icon: "plus") %>
|
||||
|
||||
<% unless chat.new_record? %>
|
||||
<%= contextual_menu_item "Edit chat title", url: edit_chat_path(chat, ctx: "chat"), icon: "pencil", turbo_frame: dom_id(chat, "title") %>
|
||||
<%= contextual_menu_destructive_item "Delete chat", chat_path(chat), turbo_confirm: "Are you sure you want to delete this chat?" %>
|
||||
<% menu.with_item(text: "Edit chat title", href: edit_chat_path(chat, ctx: "chat"), icon: "pencil", data: { turbo_frame: dom_id(chat, "title") }) %>
|
||||
<% menu.with_item(
|
||||
text: "Delete chat",
|
||||
href: chat_path(chat),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Are you sure you want to delete this chat?",
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
|
|
@ -15,17 +15,13 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_family_merchant_path(family_merchant), icon: "pencil", turbo_frame: "modal" %>
|
||||
|
||||
<%= contextual_menu_destructive_item "Delete",
|
||||
family_merchant_path(family_merchant),
|
||||
turbo_frame: "_top",
|
||||
turbo_confirm: family_merchant.transactions.any? ? {
|
||||
title: "Delete #{family_merchant.name}?",
|
||||
body: "This will remove this merchant from all transactions it has been assigned to.",
|
||||
accept: "Delete"
|
||||
} : nil %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Edit", href: edit_family_merchant_path(family_merchant), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(text: "Delete", href: family_merchant_path(family_merchant), icon: "trash-2", method: :delete, data: { turbo_confirm: {
|
||||
title: "Delete #{family_merchant.name}?",
|
||||
body: "This will remove this merchant from all transactions it has been assigned to.",
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -36,34 +36,36 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= link_to import_path(import),
|
||||
class: "block w-full py-2 px-3 space-x-2 text-primary hover:bg-gray-50 flex items-center rounded-lg" do %>
|
||||
<%= lucide_icon "eye", class: "w-5 h-5 text-secondary" %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".view"), href: import_path(import), icon: "eye") %>
|
||||
|
||||
<span><%= t(".view") %></span>
|
||||
<% end %>
|
||||
<% if import.complete? || import.revert_failed? %>
|
||||
<% menu.with_item(
|
||||
text: t(".revert"),
|
||||
href: revert_import_path(import),
|
||||
icon: "rotate-ccw",
|
||||
method: :put,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Revert import?",
|
||||
body: "This will delete transactions that were imported, but you will still be able to review and re-import your data at any time.",
|
||||
accept: "Revert"
|
||||
}
|
||||
}) %>
|
||||
|
||||
<% if import.complete? || import.revert_failed? %>
|
||||
<%= button_to revert_import_path(import),
|
||||
method: :put,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-orange-600 hover:bg-orange-50 flex items-center rounded-lg",
|
||||
data: { turbo_confirm: true } do %>
|
||||
<%= lucide_icon "rotate-ccw", class: "w-5 h-5" %>
|
||||
|
||||
<span>Revert</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button_to import_path(import),
|
||||
method: :delete,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
|
||||
data: { turbo_confirm: true } do %>
|
||||
<%= lucide_icon "trash-2", class: "w-5 h-5" %>
|
||||
|
||||
<span><%= t(".delete") %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<% menu.with_item(
|
||||
text: t(".delete"),
|
||||
href: import_path(import),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Delete import?",
|
||||
body: "This will delete the import and is not reversible.",
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<dialog id="confirm-dialog" data-controller="confirm-dialog" class="backdrop:bg-overlay bg-transparent m-auto p-1">
|
||||
<form method="dialog" class="p-4 bg-container rounded-xl shadow-border-xs space-y-4 min-w-full lg:min-w-[300px] lg:max-w-[400px]">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<h3 class="font-medium text-primary" data-confirm-dialog-target="title">Are you sure?</h3>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
|
|
|
@ -20,6 +20,13 @@
|
|||
|
||||
<%= family_stream %>
|
||||
|
||||
<% if Rails.env.development? %>
|
||||
<div class="fixed bottom-10 left-2">
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "eclipse", data: { action: "theme#toDark" }) %>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "sun", data: { action: "theme#toLight" }) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= turbo_frame_tag "modal" %>
|
||||
<%= turbo_frame_tag "drawer" %>
|
||||
|
||||
|
|
|
@ -15,19 +15,19 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_merchant_path(merchant) %>
|
||||
|
||||
<%= contextual_menu_destructive_item t(".delete"),
|
||||
merchant_path(merchant),
|
||||
turbo_frame: "_top",
|
||||
turbo_confirm: merchant.transactions.any? ? {
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
accept: t(".confirm_accept")
|
||||
} : nil %>
|
||||
</div>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), href: edit_merchant_path(merchant), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(
|
||||
text: t(".delete"),
|
||||
href: merchant_path(merchant),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
confirmText: t(".confirm_accept"),
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -107,24 +107,13 @@
|
|||
) %>
|
||||
<% end %>
|
||||
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= button_to plaid_item_path(plaid_item),
|
||||
method: :delete,
|
||||
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
|
||||
disabled: plaid_item.syncing? || plaid_item.scheduled_for_deletion?,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
accept: t(".confirm_accept")
|
||||
}
|
||||
} do %>
|
||||
<%= lucide_icon "trash-2", class: "w-5 h-5" %>
|
||||
|
||||
<span><%= t(".delete") %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item text: t(".delete"), icon: "trash-2", href: plaid_item_path(plaid_item), method: :delete, data: { turbo_confirm: {
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
accept: t(".confirm_accept"),
|
||||
variant: "destructive"
|
||||
} } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</summary>
|
||||
|
|
|
@ -44,18 +44,15 @@
|
|||
<div class="flex items-center gap-4">
|
||||
<%= render "shared/toggle_form", model: rule, attribute: :active %>
|
||||
|
||||
<%= contextual_menu icon: "more-vertical", id: "chat-menu" do %>
|
||||
<%= contextual_menu_item "Edit", url: edit_rule_path(rule), icon: "pencil", turbo_frame: "modal" %>
|
||||
|
||||
<%= contextual_menu_item "Re-apply rule", url: confirm_rule_path(rule), turbo_frame: "modal", icon: "refresh-cw" %>
|
||||
|
||||
<% turbo_confirm = {
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Edit", href: edit_rule_path(rule), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(text: "Re-apply rule", href: confirm_rule_path(rule), icon: "refresh-cw", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(text: "Delete", href: rule_path(rule), icon: "trash-2", method: :delete, data: { turbo_confirm: {
|
||||
title: "Delete rule",
|
||||
body: "Are you sure you want to delete this rule? Data affected by this rule will no longer be automatically updated. This action cannot be undone.",
|
||||
accept: "Delete rule",
|
||||
} %>
|
||||
|
||||
<%= contextual_menu_destructive_item "Delete", rule_path(rule), turbo_confirm: turbo_confirm %>
|
||||
confirmText: "Delete rule",
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
<header class="flex items-center justify-between">
|
||||
<h1 class="text-primary text-xl font-medium">Rules</h1>
|
||||
|
||||
<% turbo_confirm = {
|
||||
title: "Delete all rules",
|
||||
body: "Are you sure you want to delete all rules? This action cannot be undone.",
|
||||
accept: "Delete all rules",
|
||||
} %>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<% if @rules.any? %>
|
||||
<%= contextual_menu do %>
|
||||
<%= contextual_menu_destructive_item "Delete all rules", destroy_all_rules_path, turbo_confirm: turbo_confirm %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(
|
||||
text: "Delete all rules",
|
||||
href: destroy_all_rules_path,
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Delete all rules",
|
||||
body: "Are you sure you want to delete all rules? This action cannot be undone.",
|
||||
confirmText: "Delete all rules",
|
||||
variant: "destructive"
|
||||
}
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -8,21 +8,15 @@
|
|||
</p>
|
||||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= contextual_menu do %>
|
||||
<div class="w-48 p-1 text-sm leading-6 text-primary bg-container shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= contextual_menu_modal_action_item t(".edit"), edit_tag_path(tag) %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), href: edit_tag_path(tag), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
|
||||
<% if tag.transactions.any? %>
|
||||
<%= link_to new_tag_deletion_path(tag),
|
||||
class: "block w-full py-2 px-3 space-x-2 text-red-600 hover:bg-red-50 flex items-center rounded-lg",
|
||||
data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "trash-2", class: "w-5 h-5" %>
|
||||
<span><%= t(".delete") %></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= contextual_menu_destructive_item t(".delete"), tag_path(tag), turbo_confirm: nil %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% menu.with_item(text: t(".delete"), href: tag_path(tag), icon: "trash-2", method: :delete, data: { turbo_confirm: {
|
||||
title: "Delete tag",
|
||||
body: "Are you sure you want to delete this tag and remove it from assigned transactions? This action cannot be undone.",
|
||||
confirmText: "Delete tag",
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,24 +3,15 @@
|
|||
<h1 class="text-xl">Transactions</h1>
|
||||
<div class="flex items-center gap-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= contextual_menu do %>
|
||||
<% if Rails.env.development? %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Dev only: Sync all",
|
||||
variant: "ghost",
|
||||
href: sync_all_accounts_path,
|
||||
method: :post,
|
||||
full_width: true
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<%= contextual_menu_item "New rule", url: new_rule_path(resource_type: "transaction"), icon: "plus", turbo_frame: :modal %>
|
||||
<%= contextual_menu_item "Edit rules", url: rules_path, icon: "git-branch", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_categories"), categories_path, icon: "shapes", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_tags"), tags_path, icon: "tags", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_merchants"), family_merchants_path, icon: "store", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_imports"), imports_path, icon: "hard-drive-upload", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".import"), new_import_path, icon: "download", turbo_frame: "modal", class_name: "md:!hidden" %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Dev only: Sync all", href: sync_all_accounts_path, method: :post, icon: "refresh-cw") %>
|
||||
<% menu.with_item(text: "New rule", href: new_rule_path(resource_type: "transaction"), icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(text: "Edit rules", href: rules_path, icon: "git-branch", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit categories", href: categories_path, icon: "shapes", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit tags", href: tags_path, icon: "tags", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit merchants", href: family_merchants_path, icon: "store", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit imports", href: imports_path, icon: "hard-drive-upload", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Import", href: new_import_path, icon: "download", data: { turbo_frame: "modal", class_name: "md:!hidden" }) %>
|
||||
<% end %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue