diff --git a/app/controllers/sectors_controller.rb b/app/controllers/sectors_controller.rb
new file mode 100644
index 000000000..66a536926
--- /dev/null
+++ b/app/controllers/sectors_controller.rb
@@ -0,0 +1,72 @@
+class SectorsController < ApplicationController
+ before_action :set_sector, only: [:show, :edit, :update, :destroy]
+
+ def index
+ per_page = params[:number_of_items_per_page].presence || 25
+ unpaginated = Sector.all
+ filtered = unpaginated.sector_name(params[:sector_name])
+ .published_search(params[:published_search])
+ .order(:name)
+ @sectors = filtered.paginate(page: params[:page], per_page: per_page)
+
+ @count_display = if @sectors.total_entries == unpaginated.count
+ unpaginated.count
+ else
+ "#{@sectors.total_entries}/#{unpaginated.count}"
+ end
+ end
+
+ def show
+ end
+
+ def new
+ @sector = Sector.new
+ set_form_variables
+ end
+
+ def edit
+ set_form_variables
+ end
+
+ def create
+ @sector = Sector.new(sector_params)
+
+ if @sector.save
+ redirect_to sectors_path, notice: "Sector was successfully created."
+ else
+ set_form_variables
+ render :new, status: :unprocessable_content
+ end
+ end
+
+ def update
+ if @sector.update(sector_params)
+ redirect_to sectors_path, notice: "Sector was successfully updated.", status: :see_other
+ else
+ set_form_variables
+ render :edit, status: :unprocessable_content
+ end
+ end
+
+ def destroy
+ @sector.destroy!
+ redirect_to sectors_path, notice: "Sector was successfully destroyed."
+ end
+
+ # Optional hooks for setting variables for forms or index
+ def set_form_variables
+ end
+
+ private
+
+ def set_sector
+ @sector = Sector.find(params[:id])
+ end
+
+ # Strong parameters
+ def sector_params
+ params.require(:sector).permit(
+ :name, :published
+ )
+ end
+end
diff --git a/app/helpers/admin_dashboard_cards_helper.rb b/app/helpers/admin_dashboard_cards_helper.rb
index d192b403c..fca1dbd70 100644
--- a/app/helpers/admin_dashboard_cards_helper.rb
+++ b/app/helpers/admin_dashboard_cards_helper.rb
@@ -40,8 +40,13 @@ def user_content_cards
# -----------------------------
def reference_cards
[
- model_card(:categories, icon: "🗂️", intensity: 100),
- custom_card("Service populations", authenticated_root_path, icon: "🏭", color: :lime, intensity: 100),
+ model_card(:categories, icon: "🗂️",
+ intensity: 100,
+ params: { published_search: true }),
+ model_card(:sectors, icon: "🏭",
+ intensity: 100,
+ title: "Service populations",
+ params: { published_search: true }),
custom_card("Project statuses", authenticated_root_path, icon: "🧮", color: :emerald, intensity: 100),
custom_card("Windows types", windows_types_path, icon: "🪟"),
]
@@ -50,10 +55,10 @@ def reference_cards
# ============================================================
# CARD BUILDERS
# ============================================================
- def model_card(key, title: nil, icon:, intensity: 50)
+ def model_card(key, title: nil, icon:, intensity: 50, params: {})
{
title: title || key.to_s.humanize,
- path: polymorphic_path(key.to_s.classify.constantize),
+ path: polymorphic_path(key.to_s.classify.constantize, params),
icon: icon,
bg_color: DomainTheme.bg_class_for(key, intensity: intensity),
hover_bg_color: DomainTheme.bg_class_for(key, intensity: intensity == 50 ? 100 : intensity + 100, hover: true),
diff --git a/app/models/sector.rb b/app/models/sector.rb
index 9a4b783a5..486690fa0 100644
--- a/app/models/sector.rb
+++ b/app/models/sector.rb
@@ -14,7 +14,11 @@ class Sector < ApplicationRecord
validates_presence_of :name, uniqueness: true
# Scopes
- scope :published, -> { where(published: true).
- order(Arel.sql("CASE WHEN name = 'Other' THEN 1 ELSE 0 END, LOWER(name) ASC")) }
+ scope :published, ->(published=nil) {
+ ["true", "false"].include?(published) ? where(published: published) : where(published: true) }
+ scope :published_search, ->(published_search) { published_search.present? ? published(published_search) : all }
+ scope :sector_name, ->(sector_name) {
+ sector_name.present? ? where("sectors.name LIKE ?", "%#{sector_name}%") : all }
scope :has_taggings, -> { joins(:sectorable_items).distinct }
+
end
diff --git a/app/views/categories/_form.html.erb b/app/views/categories/_form.html.erb
index 385910fbc..887260ba0 100644
--- a/app/views/categories/_form.html.erb
+++ b/app/views/categories/_form.html.erb
@@ -53,8 +53,7 @@
<%= f.button :submit,
"Save Category",
- class: "bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700
- transition-colors duration-150" %>
+ class: "btn btn-primary" %>
diff --git a/app/views/sectors/_form.html.erb b/app/views/sectors/_form.html.erb
new file mode 100644
index 000000000..f9c90728e
--- /dev/null
+++ b/app/views/sectors/_form.html.erb
@@ -0,0 +1,48 @@
+<%= simple_form_for(@sector) do |f| %>
+
+
+
+ <%= f.error_notification %>
+ <%= render "shared/errors", resource: @sector if @sector.errors.any? %>
+
+
+
+
+
+
+ <%= f.input :name,
+ label: "Name",
+ input_html: { class: "form-control" } %>
+
+
+
+ <%= f.input :published,
+ as: :boolean,
+ label: "Published?",
+ wrapper_html: { class: "flex items-center gap-2" },
+ input_html: { class: "form-checkbox" } %>
+
+
+
+
+
+
+
+ <% if @sector.persisted? && current_user.super_user? %>
+ <%= link_to "Delete",
+ @sector,
+ method: :delete,
+ data: { confirm: "Are you sure you want to delete this category?" },
+ class: "btn btn-danger-outline" %>
+ <% end %>
+
+ <%= link_to "Cancel", sectors_path,
+ class: "btn btn-secondary-outline" %>
+
+ <%= f.button :submit,
+ "Save Sector",
+ class: "btn btn-primary" %>
+
+
+
+<% end %>
diff --git a/app/views/sectors/_search_boxes.html.erb b/app/views/sectors/_search_boxes.html.erb
new file mode 100644
index 000000000..a39c51595
--- /dev/null
+++ b/app/views/sectors/_search_boxes.html.erb
@@ -0,0 +1,39 @@
+
+
+ <%= form_with url: sectors_path,
+ method: :get,
+ local: true,
+ class: "grid grid-cols-1 md:grid-cols-5 gap-4 items-end" do |f| %>
+
+
+
+ <%= f.label :sector_name, "Name Contains",
+ class: "block text-sm font-medium text-gray-700" %>
+
+ <%= f.text_field :sector_name,
+ value: params[:sector_name],
+ class: "mt-1 block w-full rounded-md border border-gray-300 p-2",
+ oninput: "this.form.requestSubmit()" %>
+
+
+
+
+ <%= f.label :published_search, "Published",
+ class: "block text-sm font-medium text-gray-700" %>
+
+ <%= f.select :published_search,
+ options_for_select([["All", ""], ["Published", "true"], ["Hidden", "false"]], params[:published_search]),
+ {},
+ class: "mt-1 block w-full rounded-md border border-gray-300 p-2",
+ onchange: "this.form.requestSubmit()" %>
+
+
+
+
+ <%= link_to "Clear",
+ sectors_path,
+ class: "btn btn-utility-outline whitespace-nowrap" %>
+
+
+ <% end %>
+
diff --git a/app/views/sectors/edit.html.erb b/app/views/sectors/edit.html.erb
new file mode 100644
index 000000000..825418258
--- /dev/null
+++ b/app/views/sectors/edit.html.erb
@@ -0,0 +1,17 @@
+
+
+
+
+
Edit Service population
+ <%= link_to "Taggings", taggings_path(sector_names: @sector.name),
+ class: "btn btn-secondary-outline" %>
+
+
+
+
+ <%= render "form", sector: @sector %>
+
+
+
+
+
diff --git a/app/views/sectors/index.html.erb b/app/views/sectors/index.html.erb
new file mode 100644
index 000000000..b250eb974
--- /dev/null
+++ b/app/views/sectors/index.html.erb
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+ Service populations (<%= @count_display %>)
+
+ <%= link_to "New #{Sector.model_name.human.downcase}",
+ new_sector_path,
+ class: "btn btn-primary-outline" %>
+
+
+ <%= render "search_boxes" %>
+
+
+
+
+ <% if @sectors.any? %>
+
+
+
+ |
+ Name
+ |
+
+
+ Published
+ |
+
+
+ Actions
+ |
+
+
+
+
+ <% @sectors.each do |sector| %>
+
+ <%= sector.published ? "hover:bg-gray-50" : "hover:bg-gray-100" %> transition-colors duration-150">
+
+ |
+ <%= sector.name %>
+ |
+
+
+ <% if sector.published? %>
+
+ Yes
+
+ <% else %>
+
+ No
+
+ <% end %>
+ |
+
+
+
+ <%= link_to "Edit",
+ edit_sector_path(sector),
+ class: "btn btn-secondary-outline whitespace-nowrap" %>
+ <%= link_to "Taggings",
+ taggings_path(sector_names: sector.name),
+ class: "btn btn-secondary-outline whitespace-nowrap" %>
+ |
+
+
+ <% end %>
+
+
+ <% else %>
+
+
+ No <%= Sector.model_name.human.pluralize %> found.
+
+ <% end %>
+
+
+
+
+
+
+
+
+
+
diff --git a/app/views/sectors/new.html.erb b/app/views/sectors/new.html.erb
new file mode 100644
index 000000000..d5f4735ae
--- /dev/null
+++ b/app/views/sectors/new.html.erb
@@ -0,0 +1,17 @@
+
+
+
+
+
New service population
+
+
+
+
+
+
+ <%= render "form", sector: @sector %>
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/sectors/show.html.erb b/app/views/sectors/show.html.erb
new file mode 100644
index 000000000..d151b2925
--- /dev/null
+++ b/app/views/sectors/show.html.erb
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ Service population details
+
+
+
+
+ <%= link_to("Index", sectors_path, class: "btn btn-secondary-outline") %>
+ <% if current_user.super_user? %>
+ <%= link_to("Edit", edit_sector_path(@sector), class: "btn btn-primary-outline") %>
+ <% end %>
+
+
+
+
+
+
+
+
+
Name:
+
<%= @sector.name %>
+
+
+
Published:
+
<%= @sector.published %>
+
+
+
+
+
+
+
+
diff --git a/config/routes.rb b/config/routes.rb
index 6d8dc63b6..de71b048b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -52,12 +52,6 @@
resources :projects
resources :project_users
resources :quotes
- resources :users do
- member do
- get :generate_facilitator
- end
- end
- resources :user_forms
resources :monthly_reports
get 'reports/:id/edit_story', to: 'reports#edit_story', as: 'reports_edit_story'
@@ -71,6 +65,7 @@
get 'reports/annual', to: 'reports#annual'
resources :reports
+
resources :resources do
get :download
member do
@@ -80,8 +75,15 @@
post :search
end
end
+ resources :sectors
resources :story_ideas
resources :stories
+ resources :users do
+ member do
+ get :generate_facilitator
+ end
+ end
+ resources :user_forms
resources :windows_types
resources :workshop_ideas
resources :workshop_logs
diff --git a/spec/models/sector_spec.rb b/spec/models/sector_spec.rb
index aa862e9d8..de6c345f6 100644
--- a/spec/models/sector_spec.rb
+++ b/spec/models/sector_spec.rb
@@ -1,28 +1,5 @@
require 'rails_helper'
-RSpec.describe Sector do
- describe 'associations' do
- it { should have_many(:sectorable_items).dependent(:destroy) }
- it { should have_many(:workshops).through(:sectorable_items) }
- it { should have_many(:quotes).through(:workshops) }
- end
-
- describe 'validations' do
- let!(:existing_sector) { create(:sector) }
- subject { build(:sector, name: existing_sector.name) }
- it { should validate_presence_of(:name) }
- end
-
- it "has a valid factory" do
- expect(build(:sector)).to be_valid
- end
-
- describe ".published" do
- it "returns only published sectors" do
- published = create(:sector, published: true)
- unpublished = create(:sector, published: false)
-
- expect(Sector.published).to contain_exactly(published)
- end
- end
+RSpec.describe Sector, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
end
diff --git a/spec/requests/sectors_spec.rb b/spec/requests/sectors_spec.rb
new file mode 100644
index 000000000..b9515aec8
--- /dev/null
+++ b/spec/requests/sectors_spec.rb
@@ -0,0 +1,128 @@
+require 'rails_helper'
+
+RSpec.describe "/sectors", type: :request do
+
+ let(:valid_attributes) do
+ {
+ name: "Test Sector",
+ published: true
+ }
+ end
+
+ let(:invalid_attributes) do
+ {
+ name: "", # invalid: required
+ published: nil # invalid: boolean required
+ }
+ end
+
+ let(:admin) { create(:user, :admin) }
+
+ before do
+ sign_in admin
+ end
+
+ describe "GET /index" do
+ it "renders a successful response" do
+ Sector.create! valid_attributes
+ get sectors_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /show" do
+ it "renders a successful response" do
+ sector = Sector.create! valid_attributes
+ get sector_url(sector)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /new" do
+ it "renders a successful response" do
+ get new_sector_url
+ expect(response).to be_successful
+ end
+ end
+
+ describe "GET /edit" do
+ it "renders a successful response" do
+ sector = Sector.create! valid_attributes
+ get edit_sector_url(sector)
+ expect(response).to be_successful
+ end
+ end
+
+ describe "POST /create" do
+ context "with valid parameters" do
+ it "creates a new Sector" do
+ expect {
+ post sectors_url, params: { sector: valid_attributes }
+ }.to change(Sector, :count).by(1)
+ end
+
+ it "redirects to sectors index" do
+ post sectors_url, params: { sector: valid_attributes }
+ expect(response).to redirect_to(sectors_url)
+ end
+ end
+
+ context "with invalid parameters" do
+ it "does not create a new Sector" do
+ expect {
+ post sectors_url, params: { sector: invalid_attributes }
+ }.to change(Sector, :count).by(0)
+ end
+
+ it "renders a response with 422 status (i.e. to display the 'new' template)" do
+ post sectors_url, params: { sector: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_content)
+ end
+ end
+ end
+
+ describe "PATCH /update" do
+ context "with valid parameters" do
+ let(:new_attributes) {
+ skip("Add a hash of attributes valid for your model")
+ }
+
+ it "updates the requested sector" do
+ sector = Sector.create! valid_attributes
+ patch sector_url(sector), params: { sector: new_attributes }
+ sector.reload
+ skip("Add assertions for updated state")
+ end
+
+ it "redirects to the sectors index" do
+ sector = Sector.create! valid_attributes
+ patch sector_url(sector), params: { sector: new_attributes }
+ sector.reload
+ expect(response).to redirect_to(sectors_url)
+ end
+ end
+
+ context "with invalid parameters" do
+ it "renders a response with 422 status (i.e. to display the 'edit' template)" do
+ sector = Sector.create! valid_attributes
+ patch sector_url(sector), params: { sector: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_content)
+ end
+ end
+ end
+
+ describe "DELETE /destroy" do
+ it "destroys the requested sector" do
+ sector = Sector.create! valid_attributes
+ expect {
+ delete sector_url(sector)
+ }.to change(Sector, :count).by(-1)
+ end
+
+ it "redirects to the sectors list" do
+ sector = Sector.create! valid_attributes
+ delete sector_url(sector)
+ expect(response).to redirect_to(sectors_url)
+ end
+ end
+end
diff --git a/spec/routing/sectors_routing_spec.rb b/spec/routing/sectors_routing_spec.rb
new file mode 100644
index 000000000..f5187ad9e
--- /dev/null
+++ b/spec/routing/sectors_routing_spec.rb
@@ -0,0 +1,38 @@
+require "rails_helper"
+
+RSpec.describe SectorsController, type: :routing do
+ describe "routing" do
+ it "routes to #index" do
+ expect(get: "/sectors").to route_to("sectors#index")
+ end
+
+ it "routes to #new" do
+ expect(get: "/sectors/new").to route_to("sectors#new")
+ end
+
+ it "routes to #show" do
+ expect(get: "/sectors/1").to route_to("sectors#show", id: "1")
+ end
+
+ it "routes to #edit" do
+ expect(get: "/sectors/1/edit").to route_to("sectors#edit", id: "1")
+ end
+
+
+ it "routes to #create" do
+ expect(post: "/sectors").to route_to("sectors#create")
+ end
+
+ it "routes to #update via PUT" do
+ expect(put: "/sectors/1").to route_to("sectors#update", id: "1")
+ end
+
+ it "routes to #update via PATCH" do
+ expect(patch: "/sectors/1").to route_to("sectors#update", id: "1")
+ end
+
+ it "routes to #destroy" do
+ expect(delete: "/sectors/1").to route_to("sectors#destroy", id: "1")
+ end
+ end
+end
diff --git a/spec/views/sectors/edit.html.erb_spec.rb b/spec/views/sectors/edit.html.erb_spec.rb
new file mode 100644
index 000000000..b34ec7004
--- /dev/null
+++ b/spec/views/sectors/edit.html.erb_spec.rb
@@ -0,0 +1,18 @@
+require "rails_helper"
+
+RSpec.describe "sectors/edit", type: :view do
+ let(:admin) { create(:user, :admin) }
+ let(:sector) { create(:sector) }
+
+ before do
+ assign(:sector, sector)
+ allow(view).to receive(:current_user).and_return(admin)
+ end
+
+ it "renders the edit sector form with metadatum select" do
+ render
+
+ assert_select "input[name=?]", "sector[name]"
+ assert_select "input[name=?][type=checkbox]", "sector[published]"
+ end
+end
diff --git a/spec/views/sectors/index.html.erb_spec.rb b/spec/views/sectors/index.html.erb_spec.rb
new file mode 100644
index 000000000..7e7aa2ffa
--- /dev/null
+++ b/spec/views/sectors/index.html.erb_spec.rb
@@ -0,0 +1,25 @@
+require 'rails_helper'
+
+RSpec.describe "sectors/index", type: :view do
+ let(:admin) { create(:user, :admin) }
+
+ before do
+ assign(:sectors, [
+ create(:category, name: "Sector One", published: true),
+ create(:category, name: "Sector Two", published: false)
+ ])
+ allow(view).to receive(:current_user).and_return(admin)
+ end
+
+ it "renders each category with name, type, and published label" do
+ render
+
+ # NAME
+ expect(rendered).to include("Sector One")
+ expect(rendered).to include("Sector Two")
+
+ # PUBLISHED?
+ expect(rendered).to include("Yes") # for first category
+ expect(rendered).to include("No") # for second category
+ end
+end
diff --git a/spec/views/sectors/new.html.erb_spec.rb b/spec/views/sectors/new.html.erb_spec.rb
new file mode 100644
index 000000000..650e9986a
--- /dev/null
+++ b/spec/views/sectors/new.html.erb_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+RSpec.describe "sectors/new", type: :view do
+ before do
+ assign(:sector, Sector.new)
+ end
+
+ it "renders the new sector form" do
+ render
+
+ assert_select "form[action='#{sectors_path}']" do
+ assert_select "input[name='sector[name]']"
+ assert_select "input[name='sector[published]']"
+ end
+ end
+end
diff --git a/spec/views/sectors/show.html.erb_spec.rb b/spec/views/sectors/show.html.erb_spec.rb
new file mode 100644
index 000000000..7413e162e
--- /dev/null
+++ b/spec/views/sectors/show.html.erb_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+RSpec.describe "sectors/show", type: :view do
+ let(:admin) { create(:user, :admin) }
+
+ before(:each) do
+ assign(:sector, create(:sector, name: "Name", published: false))
+ allow(view).to receive(:current_user).and_return(admin)
+ end
+
+ it "renders attributes in " do
+ render
+ expect(rendered).to match(/Name/)
+ expect(rendered).to match(/false/)
+ end
+end