From 09fb22ae66a223f1190497468ca93e180a3c0aa3 Mon Sep 17 00:00:00 2001 From: Ali Date: Mon, 23 Feb 2026 23:33:06 -0500 Subject: [PATCH 1/4] feat: added controller, views, tests for reports --- app/assets/images/icons/reports-fill.svg | 2 + app/components/sidebar_component.rb | 4 +- app/controllers/reports_controller.rb | 58 +++++++++++++++++++++ app/views/reports/index.html.erb | 28 ++++++++++ config/locales/en/reports.en.yml | 23 ++++++++ config/locales/es/reports.es.yml | 23 ++++++++ config/routes.rb | 3 ++ test/controllers/reports_controller_test.rb | 35 +++++++++++++ 8 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 app/assets/images/icons/reports-fill.svg create mode 100644 app/controllers/reports_controller.rb create mode 100644 app/views/reports/index.html.erb create mode 100644 config/locales/en/reports.en.yml create mode 100644 config/locales/es/reports.es.yml create mode 100644 test/controllers/reports_controller_test.rb diff --git a/app/assets/images/icons/reports-fill.svg b/app/assets/images/icons/reports-fill.svg new file mode 100644 index 00000000..33b766b0 --- /dev/null +++ b/app/assets/images/icons/reports-fill.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/components/sidebar_component.rb b/app/components/sidebar_component.rb index 8cf5ddcc..1b3c37bc 100644 --- a/app/components/sidebar_component.rb +++ b/app/components/sidebar_component.rb @@ -21,7 +21,9 @@ def build_admin_section SIDEBAR_ITEM.new(name: t("users", scope: "components.sidebar_component.build_admin_section"), path: helpers.users_path, icon: "person-gear"), SIDEBAR_ITEM.new(name: t("regions", scope: "components.sidebar_component.build_admin_section"), - path: helpers.regions_path, icon: "compass") + path: helpers.regions_path, icon: "compass"), + SIDEBAR_ITEM.new(name: t("reports", scope: "components.sidebar_component.build_admin_section"), + path: helpers.reports_path, icon: "reports-fill") ] ) end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 00000000..9b2e1868 --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,58 @@ +class ReportsController < ApplicationController + before_action :set_report, only: %i[show edit update destroy] + + def index + @reports = Report.all + end + + def show; end + + def new + @report = Report.new + end + + def edit; end + + def create + @report = Report.new(report_params) + if @report.save + redirect_to reports_path, flash: { success: t(".success") } + else + flash.now[:error] = @report.errors.full_messages.to_sentence + render :new, status: :unprocessable_entity + end + end + + def update + if @report.update(report_params) + redirect_to reports_path, flash: { success: t(".success") } + else + flash.now[:error] = @report.errors.full_messages.to_sentence + render :edit, status: :unprocessable_entity + end + end + + def destroy + if @report.destroy + redirect_to reports_path, flash: { success: t(".success") } + else + redirect_to reports_path, flash: { error: @report.errors.full_messages.to_sentence } + end + end + + private + + def set_report + @report = Report.find(params[:id]) + rescue ActiveRecord::RecordNotFound + redirect_to reports_path, flash: { error: t("reports.not_found") } + end + + def report_params + params.expect(report: %i[start_date end_date]) + end + + def has_required_roles? + current_user.has_roles?(:admin) + end +end diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb new file mode 100644 index 00000000..7dc19d56 --- /dev/null +++ b/app/views/reports/index.html.erb @@ -0,0 +1,28 @@ +
+
+

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

+ <%= render ActionButtonComponent.new(to: new_report_path, icon: "add", colour: :primary, size: :large, turbo_stream: true) do %> + <%= t(".actions.create") %> + <% end %> +
+ <%= render ContentCardComponent.new do %> + <%= render Shared::IndexTableComponent.new(records: @reports) do |table| %> + <% table.column :start_date, header: t(".columns.start_date") do |report| %> + <%= l(report.start_date) if report.start_date %> + <% end %> + <% table.column :end_date, header: t(".columns.end_date") do |report| %> + <%= l(report.end_date) if report.end_date %> + <% end %> + <% table.column :created_at, header: t(".columns.created_at") do |report| %> + <%= l(report.created_at) %> + <% end %> + <% table.column :actions, header: t("shared.index_table_component.actions"), col_size: "90px" do |report| %> +
+ <%= render ActionButtonComponent.new(to: report_path(report), icon: "view", turbo_stream: true) %> + <%= render ActionButtonComponent.new(to: edit_report_path(report), icon: "edit", turbo_stream: true, colour: :info) %> + <%= render ActionButtonComponent.new(to: report_path(report), method: :delete, icon: "delete", colour: :error, confirm: t("reports.destroy.confirm")) %> +
+ <% end %> + <% end %> + <% end %> +
\ No newline at end of file diff --git a/config/locales/en/reports.en.yml b/config/locales/en/reports.en.yml new file mode 100644 index 00000000..556e2d4b --- /dev/null +++ b/config/locales/en/reports.en.yml @@ -0,0 +1,23 @@ +--- +en: + components: + sidebar_component: + build_admin_section: + reports: Reports + reports: + create: + success: Report created successfully. + destroy: + confirm: Are you sure you want to delete this report? + success: Report deleted successfully. + index: + actions: + create: Create Report + columns: + created_at: Created At + end_date: End Date + start_date: Start Date + title: Reports + not_found: Report not found. + update: + success: Report updated successfully. diff --git a/config/locales/es/reports.es.yml b/config/locales/es/reports.es.yml new file mode 100644 index 00000000..93caa50a --- /dev/null +++ b/config/locales/es/reports.es.yml @@ -0,0 +1,23 @@ +--- +es: + components: + sidebar_component: + build_admin_section: + reports: Informes + reports: + create: + success: El informe se creó correctamente. + destroy: + confirm: "¿Está seguro de que desea eliminar este informe?" + success: Informe eliminado exitosamente. + index: + actions: + create: Crear informe + columns: + created_at: Creado en + end_date: Fecha de finalización + start_date: Fecha de inicio + title: Informes + not_found: Informe no encontrado. + update: + success: El informe se actualizó correctamente. diff --git a/config/routes.rb b/config/routes.rb index e86eb660..fdb92f3d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,9 @@ # Region routes resources :regions, except: [:show] + # Report routes: + resources :reports, only: %i[index show edit destroy new create] + root to: "projects#index" # User routes diff --git a/test/controllers/reports_controller_test.rb b/test/controllers/reports_controller_test.rb new file mode 100644 index 00000000..0ef7ec0b --- /dev/null +++ b/test/controllers/reports_controller_test.rb @@ -0,0 +1,35 @@ +require "test_helper" + +class ReportsControllerTest < ActionDispatch::IntegrationTest + setup do + create_logged_in_admin_user + @report = create(:report) + end + + test "#index redirects to login route when a user is not authenticated" do + log_out_user + get reports_url + assert_response :redirect + assert_redirected_to login_path + end + + test "#index redirects to root route when a user is not authorized" do + create_logged_in_user + get reports_url + assert_response :redirect + assert_redirected_to root_path + end + + test "#index renders successfully when a user is an admin" do + get reports_url + assert_response :success + end + + test "#destroy successfully deletes a report when user is an admin" do + assert_difference("Report.count", -1) do + delete report_path(@report) + end + + assert_redirected_to reports_path + end +end From 8cac48fcfd4c353ec5fa13e7245cf0a5778f4d23 Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 13 Mar 2026 23:24:53 -0400 Subject: [PATCH 2/4] fix(reports): edited report creation and focused on the Index route. --- app/components/sidebar_component.rb | 4 +- app/controllers/reports_controller.rb | 60 ++++++++++++--------- app/views/reports/index.html.erb | 6 +-- config/locales/en/reports.en.yml | 15 ++++-- config/locales/es/reports.es.yml | 15 ++++-- config/routes.rb | 2 +- test/controllers/reports_controller_test.rb | 52 ++++++++++++++++++ 7 files changed, 113 insertions(+), 41 deletions(-) diff --git a/app/components/sidebar_component.rb b/app/components/sidebar_component.rb index 4c30043b..b779a472 100644 --- a/app/components/sidebar_component.rb +++ b/app/components/sidebar_component.rb @@ -23,9 +23,7 @@ def build_admin_section SIDEBAR_ITEM.new(name: t("users", scope: "components.sidebar_component.build_admin_section"), path: helpers.users_path, icon: "person-gear"), SIDEBAR_ITEM.new(name: t("regions", scope: "components.sidebar_component.build_admin_section"), - path: helpers.regions_path, icon: "compass"), - SIDEBAR_ITEM.new(name: t("reports", scope: "components.sidebar_component.build_admin_section"), - path: helpers.reports_path, icon: "reports-fill") + path: helpers.regions_path, icon: "compass") ] ) end diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 32d926d2..574a3f35 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,6 +1,4 @@ class ReportsController < ApplicationController - before_action :set_report, only: %i[show edit update destroy] - def index @reports = Report.all end @@ -17,30 +15,6 @@ def edit render :show end - def filter - start_date = parse_date(params[:start_date]) - end_date = parse_date(params[:end_date]) - - if start_date && end_date && start_date <= end_date - @start_date = params[:start_date] - @end_date = params[:end_date] - - result = ReportFilterService.new.filter( - start_date: start_date, - end_date: end_date, - project_ids: Array(params[:project_ids]), - subproject_ids: Array(params[:subproject_ids]) - ) - - @projects = result.projects - @selected_project_ids = result.selected_project_ids - @subprojects = result.subprojects - @selected_subproject_ids = result.selected_subproject_ids - end - - render :new - end - def create start_date = parse_date(params[:start_date]) end_date = parse_date(params[:end_date]) @@ -77,6 +51,40 @@ def create redirect_to edit_report_path(report), flash: { success: t(".success") } end + def destroy + @report = Report.find(params[:id]) + + if @report.destroy + redirect_to reports_path, flash: { success: t(".success") } + else + redirect_to reports_path, flash: { error: @report.errors.full_messages.to_sentence } + end + end + + def filter + start_date = parse_date(params[:start_date]) + end_date = parse_date(params[:end_date]) + + if start_date && end_date && start_date <= end_date + @start_date = params[:start_date] + @end_date = params[:end_date] + + result = ReportFilterService.new.filter( + start_date: start_date, + end_date: end_date, + project_ids: Array(params[:project_ids]), + subproject_ids: Array(params[:subproject_ids]) + ) + + @projects = result.projects + @selected_project_ids = result.selected_project_ids + @subprojects = result.subprojects + @selected_subproject_ids = result.selected_subproject_ids + end + + render :new + end + private def parse_date(value) diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb index 7817acb7..77dbd9df 100644 --- a/app/views/reports/index.html.erb +++ b/app/views/reports/index.html.erb @@ -1,7 +1,7 @@

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

- <%= render ActionButtonComponent.new(to: new_report_path, icon: "add", colour: :primary, size: :large, turbo_stream: true) do %> + <%= render ActionButtonComponent.new(to: new_report_path, icon: "add", colour: :primary, size: :large) do %> <%= t(".actions.create") %> <% end %>
@@ -18,8 +18,8 @@ <% end %> <% table.column :actions, header: t("shared.index_table_component.actions"), col_size: "90px" do |report| %>
- <%= render ActionButtonComponent.new(to: report_path(report), icon: "view", turbo_stream: true) %> - <%= render ActionButtonComponent.new(to: edit_report_path(report), icon: "edit", turbo_stream: true, colour: :info) %> + <%= render ActionButtonComponent.new(to: report_path(report), icon: "view") %> + <%= render ActionButtonComponent.new(to: edit_report_path(report), icon: "edit", colour: :info) %> <%= render ActionButtonComponent.new(to: report_path(report), method: :delete, icon: "delete", colour: :error, confirm: t("reports.destroy.confirm")) %>
<% end %> diff --git a/config/locales/en/reports.en.yml b/config/locales/en/reports.en.yml index 00cba345..3601a0ce 100644 --- a/config/locales/en/reports.en.yml +++ b/config/locales/en/reports.en.yml @@ -1,13 +1,20 @@ --- en: - components: - sidebar_component: - build_admin_section: - reports: Reports reports: create: invalid: Please provide valid dates and select at least one subproject. success: Report was successfully generated. + destroy: + confirm: Are you sure you want to delete this report? + success: Report deleted successfully. + index: + actions: + create: Create Report + columns: + created_at: Created At + end_date: End Date + start_date: Start Date + title: Reports journal_card_component: author: Author new: diff --git a/config/locales/es/reports.es.yml b/config/locales/es/reports.es.yml index 80d561b0..36e96d5e 100644 --- a/config/locales/es/reports.es.yml +++ b/config/locales/es/reports.es.yml @@ -1,13 +1,20 @@ --- es: - components: - sidebar_component: - build_admin_section: - reports: Informes reports: create: invalid: Por favor proporcione fechas válidas y seleccione al menos un subproyecto. success: El reporte fue generado exitosamente. + destroy: + confirm: "¿Está seguro de que desea eliminar este informe?" + success: Informe eliminado exitosamente. + index: + actions: + create: Crear informe + columns: + created_at: Creado en + end_date: Fecha de finalizacion + start_date: Fecha de inicio + title: Informes journal_card_component: author: Autor new: diff --git a/config/routes.rb b/config/routes.rb index beba7f3a..8a6df88f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -36,7 +36,7 @@ delete "/logout", to: "sessions#destroy" # Report routes - resources :reports, only: %i[show new create edit] do + resources :reports, only: %i[index show new create edit destroy] do collection do get :filter end diff --git a/test/controllers/reports_controller_test.rb b/test/controllers/reports_controller_test.rb index 62f0cfb5..f50fedd4 100644 --- a/test/controllers/reports_controller_test.rb +++ b/test/controllers/reports_controller_test.rb @@ -6,6 +6,7 @@ class ReportsControllerTest < ActionDispatch::IntegrationTest end [ + { route: "index", method: :get, url_helper: :reports_url, needs_report: false }, { route: "show", method: :get, url_helper: :report_url, needs_report: true }, { route: "new", method: :get, url_helper: :new_report_url, needs_report: false }, { route: "edit", method: :get, url_helper: :edit_report_url, needs_report: true }, @@ -57,6 +58,15 @@ class ReportsControllerTest < ActionDispatch::IntegrationTest assert_match aggregated_datum.additional_text, response.body end + test "#index displays reports" do + report = create(:report) + + get reports_path + assert_response :success + assert_match I18n.l(report.start_date.to_date), response.body + assert_match I18n.l(report.end_date.to_date), response.body + end + test "#filter displays projects when valid dates are provided" do subproject = create(:subproject) create(:log_entry, subproject: subproject, created_at: 1.day.ago) @@ -160,4 +170,46 @@ class ReportsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to new_report_path assert_equal I18n.t("reports.create.invalid"), flash[:error] end + + test "#create redirects with error when subproject_ids do not exist" do + assert_no_difference("Report.count") do + post reports_path, params: { + start_date: Time.zone.yesterday.to_s, + end_date: Time.zone.today.to_s, + subproject_ids: [0] + } + end + + assert_redirected_to new_report_path + assert_equal I18n.t("reports.create.invalid"), flash[:error] + end + + test "#destroy redirects to login route when a user is not authenticated" do + report = create(:report) + log_out_user + + delete report_path(report) + assert_response :redirect + assert_redirected_to login_path + end + + test "#destroy redirects to root route when a user is not authorized" do + report = create(:report) + create_logged_in_user + + delete report_path(report) + assert_response :redirect + assert_redirected_to root_path + end + + test "#destroy removes the report and redirects to index" do + report = create(:report) + + assert_difference("Report.count", -1) do + delete report_path(report) + end + + assert_redirected_to reports_path + assert_equal I18n.t("reports.destroy.success"), flash[:success] + end end From f9a275f40f8a620290944e0cc62467b3b96e261c Mon Sep 17 00:00:00 2001 From: Ali Date: Thu, 19 Mar 2026 23:38:28 -0400 Subject: [PATCH 3/4] fix(reports): removed icon and headers. --- app/assets/images/icons/reports-fill.svg | 2 -- app/views/reports/index.html.erb | 6 +++--- app/views/reports/new.html.erb | 2 +- test/controllers/reports_controller_test.rb | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 app/assets/images/icons/reports-fill.svg diff --git a/app/assets/images/icons/reports-fill.svg b/app/assets/images/icons/reports-fill.svg deleted file mode 100644 index 33b766b0..00000000 --- a/app/assets/images/icons/reports-fill.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/app/views/reports/index.html.erb b/app/views/reports/index.html.erb index 77dbd9df..2c2c925b 100644 --- a/app/views/reports/index.html.erb +++ b/app/views/reports/index.html.erb @@ -7,13 +7,13 @@
<%= render ContentCardComponent.new do %> <%= render Shared::IndexTableComponent.new(records: @reports) do |table| %> - <% table.column :start_date, header: t(".columns.start_date") do |report| %> + <% table.column :start_date do |report| %> <%= l(report.start_date) if report.start_date %> <% end %> - <% table.column :end_date, header: t(".columns.end_date") do |report| %> + <% table.column :end_date do |report| %> <%= l(report.end_date) if report.end_date %> <% end %> - <% table.column :created_at, header: t(".columns.created_at") do |report| %> + <% table.column :created_at do |report| %> <%= l(report.created_at) %> <% end %> <% table.column :actions, header: t("shared.index_table_component.actions"), col_size: "90px" do |report| %> diff --git a/app/views/reports/new.html.erb b/app/views/reports/new.html.erb index 387af156..ef0374dd 100644 --- a/app/views/reports/new.html.erb +++ b/app/views/reports/new.html.erb @@ -6,7 +6,7 @@
- <%= form_with url: filter_reports_path, method: :get, local: true, class: "space-y-4" do %> + <%= form_with url: new_report_path, method: :get, local: true, class: "space-y-4" do %>
<%= label_tag :start_date, t(".start_date"), class: "label font-semibold text-base-content" %> diff --git a/test/controllers/reports_controller_test.rb b/test/controllers/reports_controller_test.rb index eb392ba1..e5f964e6 100644 --- a/test/controllers/reports_controller_test.rb +++ b/test/controllers/reports_controller_test.rb @@ -10,8 +10,7 @@ class ReportsControllerTest < ActionDispatch::IntegrationTest { route: "show", method: :get, url_helper: :report_url, needs_report: true }, { route: "new", method: :get, url_helper: :new_report_url, needs_report: false }, { route: "edit", method: :get, url_helper: :edit_report_url, needs_report: true }, - { route: "update", method: :patch, url_helper: :report_url, needs_report: true }, - { route: "destroy", method: :delete, url_helper: :report_url, needs_report: true } + { route: "update", method: :patch, url_helper: :report_url, needs_report: true } ].each do |hash| test "##{hash[:route]} redirects to login route when a user is not authenticated" do log_out_user From 908d44d8911cead731a2930f4c3a665f86ddccfd Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 20 Mar 2026 22:53:06 -0400 Subject: [PATCH 4/4] fix(sidebar): add reports navigation item and locale keys --- app/assets/images/icons/reports-fill.svg | 2 ++ app/components/sidebar_component.rb | 2 ++ config/locales/en/components.en.yml | 1 + config/locales/es/components.es.yml | 1 + 4 files changed, 6 insertions(+) create mode 100644 app/assets/images/icons/reports-fill.svg diff --git a/app/assets/images/icons/reports-fill.svg b/app/assets/images/icons/reports-fill.svg new file mode 100644 index 00000000..33b766b0 --- /dev/null +++ b/app/assets/images/icons/reports-fill.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/components/sidebar_component.rb b/app/components/sidebar_component.rb index b779a472..10ff4a37 100644 --- a/app/components/sidebar_component.rb +++ b/app/components/sidebar_component.rb @@ -18,6 +18,8 @@ def build_admin_section items: [ SIDEBAR_ITEM.new(name: t("projects", scope: "components.sidebar_component.build_admin_section"), path: helpers.projects_path, icon: "journal-text"), + SIDEBAR_ITEM.new(name: t("reports", scope: "components.sidebar_component.build_admin_section"), + path: helpers.reports_path, icon: "reports-fill"), SIDEBAR_ITEM.new(name: t("new_report", scope: "components.sidebar_component.build_admin_section"), path: helpers.new_report_path, icon: "add"), SIDEBAR_ITEM.new(name: t("users", scope: "components.sidebar_component.build_admin_section"), diff --git a/config/locales/en/components.en.yml b/config/locales/en/components.en.yml index b057a92e..5ef9a672 100644 --- a/config/locales/en/components.en.yml +++ b/config/locales/en/components.en.yml @@ -7,6 +7,7 @@ en: new_report: New Report projects: Projects regions: Regions + reports: Reports users: Users build_projects_section: projects: Projects diff --git a/config/locales/es/components.es.yml b/config/locales/es/components.es.yml index 80e0f14e..c77e9018 100644 --- a/config/locales/es/components.es.yml +++ b/config/locales/es/components.es.yml @@ -7,6 +7,7 @@ es: new_report: Nuevo Reporte projects: Proyectos regions: Regiones + reports: Reportes users: Usuarios build_projects_section: projects: Proyectos