diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index d78b8f31..1dd427e8 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -102,6 +102,10 @@ .btn--primary { @apply bg-gray-900 text-white hover:bg-gray-700; } + + .btn--light { + @apply bg-gray-25 border border-alpha-black-200 text-gray-900 hover:bg-gray-50; + } } /* Small, single purpose classes that should take precedence over other styles */ diff --git a/app/controllers/institutions_controller.rb b/app/controllers/institutions_controller.rb index d87d551e..ba5f719d 100644 --- a/app/controllers/institutions_controller.rb +++ b/app/controllers/institutions_controller.rb @@ -23,6 +23,11 @@ class InstitutionsController < ApplicationController redirect_to accounts_path, notice: t(".success") end + def sync + @institution.sync + redirect_back_or_to accounts_path, notice: t(".success") + end + private def institution_params diff --git a/app/models/institution.rb b/app/models/institution.rb index 3d9f13db..d34ecd0e 100644 --- a/app/models/institution.rb +++ b/app/models/institution.rb @@ -4,4 +4,22 @@ class Institution < ApplicationRecord has_one_attached :logo scope :alphabetically, -> { order(name: :asc) } + + def sync + accounts.active.each do |account| + if account.needs_sync? + account.sync + end + end + + update! last_synced_at: Time.now + end + + def syncing? + accounts.active.any? { |account| account.syncing? } + end + + def has_issues? + accounts.active.any? { |account| account.has_issues? } + end end diff --git a/app/views/accounts/_account.html.erb b/app/views/accounts/_account.html.erb index 8a343360..e8627e69 100644 --- a/app/views/accounts/_account.html.erb +++ b/app/views/accounts/_account.html.erb @@ -4,7 +4,17 @@
"> <%= account.name[0].upcase %>
- <%= link_to account.name, account, class: [(account.is_active ? "text-gray-900" : "text-gray-400"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %> + +
+ <%= link_to account.name, account, class: [(account.is_active ? "text-gray-900" : "text-gray-400"), "text-sm font-medium hover:underline"], data: { turbo_frame: "_top" } %> + <% if account.has_issues? %> +
+ <%= lucide_icon "alert-octagon", class: "shrink-0 w-4 h-4" %> + <%= tag.span t(".has_issues") %> + <%= link_to t(".troubleshoot"), issue_path(account.issues.first), class: "underline", data: { turbo_frame: :drawer } %> +
+ <% end %> +
<%= link_to edit_account_path(account), data: { turbo_frame: :modal }, class: "group-hover/account:flex hidden hover:opacity-80 items-center justify-center" do %> <%= lucide_icon "pencil-line", class: "w-4 h-4 text-gray-500" %> diff --git a/app/views/accounts/_institution_accounts.html.erb b/app/views/accounts/_institution_accounts.html.erb index ee50e3bb..9554f1f4 100644 --- a/app/views/accounts/_institution_accounts.html.erb +++ b/app/views/accounts/_institution_accounts.html.erb @@ -1,40 +1,62 @@ <%# locals: (institution:) %>
- - <%= lucide_icon "chevron-right", class: "group-open:transform group-open:rotate-90 text-gray-500 w-5" %> + +
+ <%= lucide_icon "chevron-right", class: "group-open:transform group-open:rotate-90 text-gray-500 w-5" %> -
- <% if institution_logo(institution) %> - <%= image_tag institution_logo(institution), class: "rounded-full h-full w-full" %> - <% else %> -
- <%= tag.p institution.name.first.upcase, class: "text-blue-600 text-xs font-medium" %> -
- <% end %> +
+ <% if institution_logo(institution) %> + <%= image_tag institution_logo(institution), class: "rounded-full h-full w-full" %> + <% else %> +
+ <%= tag.p institution.name.first.upcase, class: "text-blue-600 text-xs font-medium" %> +
+ <% end %> +
+ +
+ <%= link_to institution.name, edit_institution_path(institution), data: { turbo_frame: :modal }, class: "font-medium text-gray-900 hover:underline" %> + <% if institution.has_issues? %> +
+ <%= lucide_icon "alert-octagon", class: "shrink-0 w-4 h-4" %> + <%= tag.span t(".has_issues") %> +
+ <% elsif institution.syncing? %> +
+ <%= lucide_icon "loader", class: "w-4 h-4 animate-pulse" %> + <%= tag.span t(".syncing") %> +
+ <% else %> +

<%= institution.last_synced_at ? t(".status", last_synced_at: time_ago_in_words(institution.last_synced_at)) : t(".status_never") %>

+ <% end %> +
- <%= link_to institution.name, edit_institution_path(institution), data: { turbo_frame: :modal }, class: "text-sm font-medium text-gray-900 ml-1 mr-auto hover:underline" %> +
+ <%= button_to sync_institution_path(institution), method: :post, class: "text-gray-900 flex hover:text-gray-800 items-center text-sm font-medium hover:underline" do %> + <%= lucide_icon "refresh-cw", class: "w-4 h-4" %> + <% end %> - <%= contextual_menu do %> -
- <%= link_to new_account_path(institution_id: institution.id), + <%= contextual_menu do %> +
+ <%= link_to new_account_path(institution_id: institution.id), class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg", data: { turbo_frame: :modal } do %> - <%= lucide_icon "plus", class: "w-5 h-5 text-gray-500" %> + <%= lucide_icon "plus", class: "w-5 h-5 text-gray-500" %> - <%= t(".add_account_to_institution") %> - <% end %> + <%= t(".add_account_to_institution") %> + <% end %> - <%= link_to edit_institution_path(institution), + <%= link_to edit_institution_path(institution), class: "block w-full py-2 px-3 space-x-2 text-gray-900 hover:bg-gray-50 flex items-center rounded-lg", data: { turbo_frame: :modal } do %> - <%= lucide_icon "pencil-line", class: "w-5 h-5 text-gray-500" %> + <%= lucide_icon "pencil-line", class: "w-5 h-5 text-gray-500" %> - <%= t(".edit") %> - <% end %> + <%= t(".edit") %> + <% end %> - <%= button_to institution_path(institution), + <%= button_to institution_path(institution), 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: { @@ -44,13 +66,13 @@ accept: t(".confirm_accept") } } do %> - <%= lucide_icon "trash-2", class: "w-5 h-5" %> + <%= lucide_icon "trash-2", class: "w-5 h-5" %> - <%= t(".delete") %> - <% end %> -
- - <% end %> + <%= t(".delete") %> + <% end %> +
+ <% end %> +
diff --git a/app/views/accounts/_sync_all_button.html.erb b/app/views/accounts/_sync_all_button.html.erb deleted file mode 100644 index c24ee93a..00000000 --- a/app/views/accounts/_sync_all_button.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -<%= button_to sync_all_accounts_path, method: :post, class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2", title: "Sync All" do %> - <%= lucide_icon "refresh-cw", class: "w-5 h-5" %> -<% end %> diff --git a/app/views/accounts/index.html.erb b/app/views/accounts/index.html.erb index ec9294a9..055db9be 100644 --- a/app/views/accounts/index.html.erb +++ b/app/views/accounts/index.html.erb @@ -18,14 +18,17 @@
<% end %> + <%= button_to sync_all_accounts_path, class: "btn btn--light flex items-center gap-2", title: "Sync All" do %> + <%= lucide_icon "refresh-cw", class: "w-5 h-5" %> + <%= t(".sync_all") %> + <% end %> + <%= link_to new_account_path, data: { turbo_frame: "modal" }, - class: "rounded-lg bg-gray-900 text-white flex items-center gap-1 justify-center hover:bg-gray-700 px-3 py-2" do %> + class: "btn btn--primary flex items-center gap-1" do %> <%= lucide_icon("plus", class: "w-5 h-5") %>

<%= t(".new_account") %>

<% end %> - - <%= render "sync_all_button" %> @@ -38,7 +41,9 @@ <%= render "institution_accounts", institution: %> <% end %> - <%= render "institutionless_accounts", accounts: @accounts %> + <% if @accounts.any? %> + <%= render "institutionless_accounts", accounts: @accounts %> + <% end %> <% end %> diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml index 7dcc5a9a..f75cac51 100644 --- a/config/locales/views/accounts/en.yml +++ b/config/locales/views/accounts/en.yml @@ -1,6 +1,9 @@ --- en: accounts: + account: + has_issues: Issue detected. + troubleshoot: Troubleshoot accountables: property: area_unit: Area unit @@ -63,6 +66,7 @@ en: accounts: Accounts add_institution: Add institution new_account: New account + sync_all: Sync all institution_accounts: add_account_to_institution: Add new account confirm_accept: Delete institution @@ -72,7 +76,11 @@ en: confirm_title: Delete financial institution? delete: Delete institution edit: Edit institution + has_issues: Issue detected, see accounts new_account: Add account + status: Last synced %{last_synced_at} ago + status_never: Requires data sync + syncing: Syncing... institutionless_accounts: other_accounts: Other accounts new: diff --git a/config/locales/views/institutions/en.yml b/config/locales/views/institutions/en.yml index 62fafa97..d90aec86 100644 --- a/config/locales/views/institutions/en.yml +++ b/config/locales/views/institutions/en.yml @@ -11,5 +11,7 @@ en: name: Financial institution name new: new_institution: New financial institution + sync: + success: Institution sync started update: success: Institution updated diff --git a/config/routes.rb b/config/routes.rb index 9acbdbe1..13469d26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -95,7 +95,9 @@ Rails.application.routes.draw do end end - resources :institutions, except: %i[index show] + resources :institutions, except: %i[index show] do + post :sync, on: :member + end resources :invite_codes, only: %i[index create] resources :issues, only: :show diff --git a/db/migrate/20240911143158_add_last_synced_at_institution.rb b/db/migrate/20240911143158_add_last_synced_at_institution.rb new file mode 100644 index 00000000..b4b791eb --- /dev/null +++ b/db/migrate/20240911143158_add_last_synced_at_institution.rb @@ -0,0 +1,5 @@ +class AddLastSyncedAtInstitution < ActiveRecord::Migration[7.2] + def change + add_column :institutions, :last_synced_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 7833d7ad..8c5c2483 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2024_08_23_125526) do +ActiveRecord::Schema[7.2].define(version: 2024_09_11_143158) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -318,6 +318,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_08_23_125526) do t.uuid "family_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.datetime "last_synced_at" t.index ["family_id"], name: "index_institutions_on_family_id" end diff --git a/test/controllers/institutions_controller_test.rb b/test/controllers/institutions_controller_test.rb index 2b285bc1..ce5d9664 100644 --- a/test/controllers/institutions_controller_test.rb +++ b/test/controllers/institutions_controller_test.rb @@ -52,4 +52,11 @@ class InstitutionsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to accounts_url assert_equal "Institution deleted", flash[:notice] end + + test "can sync institution" do + post sync_institution_url(@institution) + + assert_redirected_to accounts_url + assert_equal "Institution sync started", flash[:notice] + end end