mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-03 04:25:21 +02:00
Basic transaction categories CRUD actions (inline) (#601)
* Fix dropdown issues and add dummy transaction category modal * Minor namings tweaks * Add search type * Use new menu controller * Complete basic transaction category inline CRUD actions * Fix lint error --------- Co-authored-by: Jakub Kottnauer <jk@jakubkottnauer.com>
This commit is contained in:
parent
315c4bf1ec
commit
d29d465a3c
23 changed files with 254 additions and 101 deletions
|
@ -1,45 +0,0 @@
|
|||
<%# locals: (transaction:) %>
|
||||
<div class="relative" data-controller="menu">
|
||||
<button data-menu-target="button" class="flex">
|
||||
<%= render partial: "shared/category_badge", locals: transaction.category.nil? ? {} : { name: transaction.category.name, color: transaction.category.color } %>
|
||||
</button>
|
||||
<div data-menu-target="content" class="absolute z-10 hidden w-screen mt-2 max-w-min cursor-default">
|
||||
<div class="w-64 text-sm font-semibold leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="flex flex-col" data-controller="list-filter">
|
||||
<div class="grow p-1.5">
|
||||
<div class="relative flex items-center bg-white border border-gray-200 rounded-lg">
|
||||
<input placeholder="Search" type="search" class="placeholder:text-sm placeholder:text-gray-500 font-normal h-10 relative pl-10 w-full border-none rounded-lg" data-list-filter-target="input" data-action="list-filter#filter" />
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 ml-2 absolute inset-0 transform top-1/2 -translate-y-1/2") %>
|
||||
</div>
|
||||
</div>
|
||||
<%= form_with model: transaction, namespace: dom_id(transaction), html: { data: { controller: "auto-submit-form", list_filter_target: "list" }, class: "flex flex-col gap-0.5 p-1.5 mt-0.5 mr-2 max-h-64 overflow-y-scroll scrollbar" } do |form| %>
|
||||
<div class="py-8 pl-4 mt-0.5 mr-2 text-gray-500 hidden" data-list-filter-target="emptyMessage">
|
||||
No categories found
|
||||
</div>
|
||||
<% Current.family.transaction_categories.each do |category| %>
|
||||
<% is_selected = category.id == transaction.category.try(:id) %>
|
||||
<%= content_tag :div, class: ["filterable-item flex items-center hover:bg-gray-25 border-none rounded-lg px-2 py-1 group", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
|
||||
<%= form.radio_button :category_id, category.id, class: "hidden", data: { auto_submit_form_target: "auto" } %>
|
||||
<%= label dom_id(transaction), :transaction_category_id, value: category.id, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
|
||||
<span class="w-5 h-5">
|
||||
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
|
||||
</span>
|
||||
<%= render partial: "shared/category_badge", locals: { name: category.name, color: category.color } %>
|
||||
<% end %>
|
||||
<button class="ml-auto flex items-center justify-center hover:bg-gray-50 w-8 h-8 rounded-lg invisible group-hover:visible cursor-not-allowed" type="button" disabled>
|
||||
<%= lucide_icon("more-horizontal", class: "w-5 h-5 text-gray-500") %>
|
||||
</button>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<hr/>
|
||||
<div class="p-1.5 w-full">
|
||||
<button class="cursor-not-allowed flex text-sm font-medium items-center gap-2 text-gray-500 w-full rounded-lg p-2 hover:bg-gray-100" disabled>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
Add new
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -11,7 +11,7 @@
|
|||
<%= lucide_icon("list-filter", class: "w-5 h-5 text-gray-500") %>
|
||||
<p class="text-sm font-medium text-gray-900">Filter</p>
|
||||
</button>
|
||||
<div data-menu-target="content" class="absolute z-10 top-12 right-0 border border-alpha-black-25 bg-white rounded-lg shadow-xs min-w-[450px]">
|
||||
<div data-menu-target="content" class="hidden absolute z-10 top-12 right-0 border border-alpha-black-25 bg-white rounded-lg shadow-xs min-w-[450px]">
|
||||
<div data-controller="tabs" data-tabs-active-class="border-b-2 border-b-black text-gray-900" data-tabs-default-tab-value="txn-account-filter">
|
||||
<div class="flex items-center px-3 text-sm font-medium text-gray-500 gap-4 border-b border-b-alpha-black-50">
|
||||
<button class="py-2 border-b-2" type="button" data-id="txn-account-filter" data-tabs-target="btn" data-action="tabs#select">Account</button>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
<div class="w-48">
|
||||
<%= render partial: "transactions/category_dropdown", locals: { transaction: } %>
|
||||
<%= render partial: "transactions/categories/menu", locals: { transaction: } %>
|
||||
</div>
|
||||
<div>
|
||||
<p><%= transaction.account.name %></p>
|
||||
|
|
4
app/views/transactions/categories/_badge.html.erb
Normal file
4
app/views/transactions/categories/_badge.html.erb
Normal file
|
@ -0,0 +1,4 @@
|
|||
<%# locals: (name: "Uncategorized", color: Transaction::Category::UNCATEGORIZED_COLOR) %>
|
||||
<% background_color = "color-mix(in srgb, #{color} 5%, white)" %>
|
||||
<% border_color = "color-mix(in srgb, #{color} 10%, white)" %>
|
||||
<span class="border text-sm font-medium px-2.5 py-1 rounded-full cursor-pointer" style="background-color: <%= background_color %>; border-color: <%= border_color %>; color: <%= color %>"><%= name %></span>
|
39
app/views/transactions/categories/_menu.html.erb
Normal file
39
app/views/transactions/categories/_menu.html.erb
Normal file
|
@ -0,0 +1,39 @@
|
|||
<%# locals: (transaction:) %>
|
||||
<div class="relative" data-controller="menu">
|
||||
<button data-menu-target="button" class="flex">
|
||||
<%= render partial: "transactions/categories/badge", locals: transaction.category.nil? ? {} : { name: transaction.category.name, color: transaction.category.color } %>
|
||||
</button>
|
||||
<div data-menu-target="content" class="absolute z-10 hidden w-screen mt-2 max-w-min cursor-default">
|
||||
<div class="w-64 text-sm font-semibold leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="flex flex-col relative" data-controller="list-filter">
|
||||
<div class="grow p-1.5">
|
||||
<div class="relative flex items-center bg-white border border-gray-200 rounded-lg">
|
||||
<input placeholder="Search" type="search" class="placeholder:text-sm placeholder:text-gray-500 font-normal h-10 relative pl-10 w-full border-none rounded-lg" data-list-filter-target="input" data-action="list-filter#filter" />
|
||||
<%= lucide_icon("search", class: "w-5 h-5 text-gray-500 ml-2 absolute inset-0 transform top-1/2 -translate-y-1/2") %>
|
||||
</div>
|
||||
</div>
|
||||
<div data-list-filter-target="list" class="flex flex-col gap-0.5 p-1.5 mt-0.5 mr-2 max-h-64 overflow-y-scroll scrollbar">
|
||||
<div class="pb-2 pl-4 mr-2 text-gray-500 hidden" data-list-filter-target="emptyMessage">
|
||||
No categories found
|
||||
</div>
|
||||
<% sorted_categories = Current.family.transaction_categories.sort_by { |category| category.id == transaction.category_id ? 0 : 1 } %>
|
||||
<% sorted_categories.each do |category| %>
|
||||
<%= render partial: "transactions/categories/dropdown/row", locals: { category:, transaction: } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<hr/>
|
||||
<div data-controller="menu" class="relative p-1.5 w-full">
|
||||
<button data-menu-target="button" class="flex text-sm font-medium items-center gap-2 text-gray-500 w-full rounded-lg p-2 hover:bg-gray-100">
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
Add new
|
||||
</button>
|
||||
<div data-menu-target="content" class="hidden absolute bottom-14 right-0">
|
||||
<div class="w-96 text-sm font-semibold leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<%= render partial: "transactions/categories/dropdown/form" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
15
app/views/transactions/categories/dropdown/_edit.html.erb
Normal file
15
app/views/transactions/categories/dropdown/_edit.html.erb
Normal file
|
@ -0,0 +1,15 @@
|
|||
<%# locals: (category:) %>
|
||||
<div class="w-96 text-sm font-semibold leading-6 text-gray-900 bg-white shadow-lg shrink rounded-xl ring-1 ring-gray-900/5">
|
||||
<div class="flex flex-col">
|
||||
<%= render partial: "transactions/categories/dropdown/form", locals: { category: } %>
|
||||
<hr/>
|
||||
<div class="p-1.5 w-full">
|
||||
<%= button_to transactions_category_path(category),
|
||||
method: :delete,
|
||||
class: "flex text-sm font-medium items-center gap-2 text-red-600 w-full rounded-lg p-2 hover:bg-gray-100",
|
||||
data: { turbo: false } do %>
|
||||
<%= lucide_icon("trash-2", class: "w-5 h-5") %> Delete category
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
28
app/views/transactions/categories/dropdown/_form.html.erb
Normal file
28
app/views/transactions/categories/dropdown/_form.html.erb
Normal file
|
@ -0,0 +1,28 @@
|
|||
<%# locals: (category: nil) %>
|
||||
<%= form_with url: category ? transactions_category_path(category) : transactions_categories_path, method: category ? :patch : :post, scope: :transaction_category, html: { class: "text-sm font-semibold leading-6 text-gray-900" }, data: { turbo: false } do |form| %>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col p-1.5 gap-1.5">
|
||||
<div class="relative flex items-center border border-gray-200 rounded-lg">
|
||||
<%= form.text_field :name, value: category&.name, placeholder: "Enter Category name", class: "text-sm font-normal placeholder:text-gray-500 h-10 relative pl-6 w-full border-none rounded-lg" %>
|
||||
</div>
|
||||
<div class="p-2 overflow-x-auto">
|
||||
<div data-controller="select" data-select-active-class="bg-gray-200" data-select-selected-value="<%= category&.color || Transaction::Category::COLORS[0] %>">
|
||||
<%= form.hidden_field :color, data: { select_target: "input" } %>
|
||||
<ul data-select-target="list" class="flex gap-2 items-center">
|
||||
<% Transaction::Category::COLORS.each do |color| %>
|
||||
<li tabindex="0" data-select-target="option" data-action="click->select#selectOption" data-value="<%= color %>" class="flex shrink-0 justify-center items-center w-6 h-6 cursor-pointer hover:bg-gray-200 rounded-full">
|
||||
<div style="background-color: <%= color %>" class="shrink-0 w-4 h-4 rounded-full"></div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="p-1.5 w-full">
|
||||
<%= form.button "Create category", class: "flex text-sm font-medium items-center gap-2 text-gray-900 w-full rounded-lg p-2 hover:bg-gray-100" do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %> <%= category.nil? ? "Create" : "Update" %> category
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
18
app/views/transactions/categories/dropdown/_row.html.erb
Normal file
18
app/views/transactions/categories/dropdown/_row.html.erb
Normal file
|
@ -0,0 +1,18 @@
|
|||
<%# locals: (category:, transaction:) %>
|
||||
<% is_selected = transaction.category_id == category.id %>
|
||||
<%= content_tag :div, class: ["filterable-item flex justify-between items-center border-none rounded-lg px-2 py-1 group w-full", { "bg-gray-25": is_selected }], data: { filter_name: category.name } do %>
|
||||
<%= button_to transaction_path(transaction, transaction: { category_id: category.id }), method: :patch, class: "flex w-full items-center gap-1.5 cursor-pointer" do %>
|
||||
<span class="w-5 h-5">
|
||||
<%= lucide_icon("check", class: "w-5 h-5 text-gray-500") if is_selected %>
|
||||
</span>
|
||||
<%= render partial: "transactions/categories/badge", locals: { name: category.name, color: category.color } %>
|
||||
<% end %>
|
||||
<div data-controller="menu">
|
||||
<button data-menu-target="button" type="button" class="flex items-center justify-center hover:bg-gray-50 w-8 h-8 rounded-lg">
|
||||
<%= lucide_icon("more-horizontal", class: "w-5 h-5 text-gray-500") %>
|
||||
</button>
|
||||
<div data-menu-target="content" class="absolute z-30 hidden w-screen mt-2 max-w-min">
|
||||
<%= render partial: "transactions/categories/dropdown/edit", locals: { category: } %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
|
@ -9,7 +9,7 @@
|
|||
<div class="filterable-item flex items-center gap-2 p-2" data-filter-name="<%= transaction_category.name %>">
|
||||
<%= form.check_box :category_id_in, { "data-auto-submit-form-target": "auto", multiple: true, class: "rounded-sm border-gray-300 text-indigo-600 shadow-xs focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" }, transaction_category.id, nil %>
|
||||
<%= form.label :category_id_in, transaction_category.name, value: transaction_category.id, class: "text-sm text-gray-900" do %>
|
||||
<%= render partial: "shared/category_badge", locals: { name: transaction_category.name, color: transaction_category.color } %>
|
||||
<%= render partial: "transactions/categories/badge", locals: { name: transaction_category.name, color: transaction_category.color } %>
|
||||
<%end%>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue