# frozen_string_literal: true # rubocop:disable Metrics/ModuleLength module AdminHelper # Renders a table for index pages with sortable columns # @param collection [ActiveRecord::Relation] The records to display # @param columns [Array] Column definitions with :attribute, :label, :sortable keys # @param options [Hash] Additional options for the table def admin_table(collection, columns: [], **options) title = options.delete(:title) actions = options.delete(:actions) render "admin/shared/table", collection: collection, columns: columns, options: options, title: title, actions: actions end # Renders attribute rows for show pages # @param resource [ActiveRecord::Base] The record to display # @param attributes [Array] Attributes to display def admin_show_attributes(resource, attributes: []) render "admin/shared/show_attributes", resource: resource, attributes: attributes end # Renders breadcrumbs for navigation # @param breadcrumbs [Array] Breadcrumb items with :label and :path keys def admin_breadcrumbs(breadcrumbs = []) render "admin/shared/breadcrumbs", breadcrumbs: breadcrumbs end # Renders action buttons (Edit, Delete, Custom) # @param resource [ActiveRecord::Base] The record for actions # @param actions [Array] Actions to include (:edit, :delete, :custom) # @param custom_actions [Array] Custom action definitions def admin_actions(resource, actions: %i[edit delete], custom_actions: []) render "admin/shared/actions", resource: resource, actions: actions, custom_actions: custom_actions end # Formats an attribute value for display # @param resource [ActiveRecord::Base] The record # @param attribute [Symbol] The attribute name # @return [String] Formatted value def format_attribute(resource, attribute) value = resource.send(attribute) case value when TrueClass, FalseClass boolean_badge(value) when Time, DateTime, Date value.strftime("%B %d, %Y") when ActiveRecord::Base format_association(value) when nil content_tag(:span, "—", class: "text-gray-400") else value.to_s end end # Renders a boolean badge # @param value [Boolean] The boolean value # @return [String] HTML badge def boolean_badge(value) if value content_tag( :span, "Yes", class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800" ) else content_tag( :span, "No", class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800" ) end end # Generates a sort link for table headers # @param column [Symbol] The column name # @param label [String] The display label # @return [String] HTML link def sort_link(column, label) direction = params[:sort] == column.to_s && params[:direction] == "asc" ? "desc" : "asc" icon = sort_icon(column) # Build URL with query parameters url = url_for(sort: column, direction: direction, only_path: true) link_to url, class: "group inline-flex items-center" do safe_join( [ label, content_tag( :span, icon, class: "ml-2 flex-none rounded text-gray-900 group-hover:text-gray-900" ) ] ) end end # Returns the sort icon for a column # @param column [Symbol] The column name # @return [String] Icon HTML def sort_icon(column) if params[:sort] == column.to_s params[:direction] == "asc" ? "↑" : "↓" else "⇅" end end # Renders search and filter form # @param filters [Array] Filter definitions with :name, :label, :options keys # @param search_placeholder [String] Placeholder text for search field def admin_search_filter(filters: [], search_placeholder: "Search...") render "admin/shared/search_filter", filters: filters, search_placeholder: search_placeholder end # Determines the current discard filter state based on query parameters # @return [Symbol] :active, :discarded, or :all def current_discard_filter if params[:discarded].present? :discarded elsif params[:all].present? :all else :active end end # Returns the correct model for routing purposes # Handles STI (Single Table Inheritance) by returning the base class # @param record [ActiveRecord::Base] The record # @return [ActiveRecord::Base] The record or its base class for routing def route_model(record) if record.class.base_class == record.class record else record.becomes(record.class.base_class) end end # Renders the archive/activate toggle button for a classroom # @param classroom [Classroom] The classroom record # @return [String] HTML button def classroom_archive_toggle_button(classroom) if classroom.archived? activate_button(classroom) else archive_button(classroom) end end # Renders the discard/restore action button for a soft-deletable resource # @param resource [ActiveRecord::Base] The resource record (must respond_to :discarded?) # @return [String] HTML button or link def discard_restore_action(resource) if resource.discarded? restore_button(resource) else discard_link(resource) end end # Returns the appropriate show path for a user based on their type # @param user [User] The user record # @return [String] Path to the type-specific show page def user_show_path(user) case user.type when "Student" admin_student_path(user) when "Teacher" admin_teacher_path(user) else admin_user_path(user) end end private def format_association(value) # Use presenter if available, otherwise fall back to to_s presenter_class = "#{value.class.name}Presenter".safe_constantize display_value = if presenter_class presenter_class.new(value).display_name else value.to_s end # Try to link to the resource, but fall back to text if route doesn't exist begin link_to display_value, [:admin, route_model(value)] rescue NoMethodError, ActionController::UrlGenerationError display_value end end def restore_button(resource) resource_name = resource.class.name.underscore restore_path = send("restore_admin_#{resource_name}_path", resource) button_to "Restore", restore_path, method: :patch, data: { turbo_confirm: "Are you sure you want to restore this #{resource_name.humanize.downcase}?" }, class: "text-green-600 hover:text-green-800", form: { style: "display: inline;" } end def discard_link(resource) resource_name = resource.class.name.underscore resource_path = send("admin_#{resource_name}_path", resource) link_to "Archive", resource_path, data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to archive this #{resource_name.humanize.downcase}?" }, # rubocop:disable Layout/LineLength class: "text-red-600 hover:text-red-800" end def activate_button(classroom) button_class = "inline-flex items-center px-4 py-2 border border-green-300 shadow-sm " \ "text-sm font-medium rounded-md text-green-700 bg-white hover:bg-green-50" link_to toggle_archive_admin_classroom_path(classroom), data: { turbo_method: :patch, turbo_confirm: "Are you sure you want to activate this classroom?" }, class: button_class do safe_join( [ content_tag(:i, "", class: "fas fa-check-circle -ml-1 mr-2 h-5 w-5 text-green-500"), "Activate" ] ) end end def archive_button(classroom) button_class = "inline-flex items-center px-4 py-2 border border-yellow-300 shadow-sm " \ "text-sm font-medium rounded-md text-yellow-700 bg-white hover:bg-yellow-50" link_to toggle_archive_admin_classroom_path(classroom), data: { turbo_method: :patch, turbo_confirm: "Are you sure you want to archive this classroom?" }, class: button_class do safe_join( [ content_tag(:i, "", class: "fas fa-archive -ml-1 mr-2 h-5 w-5 text-yellow-500"), "Archive" ] ) end end end # rubocop:enable Metrics/ModuleLength