mirror of
https://github.com/edcommonwealth/Dashboard.git
synced 2026-03-07 13:38:12 -08:00
chore: start adding browse view
This commit is contained in:
parent
a538eb72f2
commit
f71f88a4ac
12 changed files with 296 additions and 87 deletions
|
|
@ -1,8 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AnalyzeController < SqmApplicationController
|
||||
module Dashboard
|
||||
class AnalyzeController < SqmApplicationController
|
||||
def index
|
||||
@presenter = Analyze::Presenter.new(params:, school: @school, academic_year: @academic_year)
|
||||
@background ||= BackgroundPresenter.new(num_of_columns: @presenter.graph.columns.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CategoriesController < SqmApplicationController
|
||||
module Dashboard
|
||||
class CategoriesController < SqmApplicationController
|
||||
helper GaugeHelper
|
||||
|
||||
def show
|
||||
|
|
@ -8,4 +9,5 @@ class CategoriesController < SqmApplicationController
|
|||
|
||||
@category = CategoryPresenter.new(category: Category.find_by_slug(params[:id]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
Point = Struct.new(:x, :y)
|
||||
Rect = Struct.new(:x, :y, :width, :height)
|
||||
|
||||
module GaugeHelper
|
||||
module Dashboard
|
||||
module GaugeHelper
|
||||
def outer_radius
|
||||
100
|
||||
end
|
||||
|
|
@ -96,4 +96,5 @@ module GaugeHelper
|
|||
def coordinates_for(point)
|
||||
"#{point.x} #{point.y}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
module Dashboard
|
||||
class Race < ApplicationRecord
|
||||
include FriendlyId
|
||||
has_many :dashboard_student_races
|
||||
has_many :dashboard_students, through: :student_races
|
||||
has_and_belongs_to_many :students, join_table: :dashboard_student_races, class_name: "Student",
|
||||
foreign_key: :dashboard_student_id, association_foreign_key: :dashboard_student_id
|
||||
|
||||
friendly_id :designation, use: [:slugged]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
module Dashboard
|
||||
class Student < ApplicationRecord
|
||||
# has_many :dashboard_survey_item_responses
|
||||
has_many :dashboard_student_races
|
||||
has_and_belongs_to_many :races, join_table: :student_races
|
||||
has_and_belongs_to_many :races, join_table: :dashboard_student_races, class_name: "Race",
|
||||
foreign_key: :dashboard_race_id, association_foreign_key: :dashboard_race_id
|
||||
|
||||
encrypts :lasid, deterministic: true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module Dashboard
|
|||
def self.grouped_responses(school:, academic_year:)
|
||||
@grouped_responses ||= Hash.new do |memo, (school, academic_year)|
|
||||
memo[[school, academic_year]] =
|
||||
SurveyItemResponse.where(school:, academic_year:).group(:survey_item_id).average(:likert_score)
|
||||
SurveyItemResponse.where(school:, academic_year:).group(:dashboard_survey_item_id).average(:likert_score)
|
||||
end
|
||||
@grouped_responses[[school, academic_year]]
|
||||
end
|
||||
|
|
|
|||
42
app/views/dashboard/categories/_data_item_section.html.erb
Normal file
42
app/views/dashboard/categories/_data_item_section.html.erb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<div class="accordion-item">
|
||||
<h3 class="accordion-header measure-accordion-header" id="<%= data_item_section.id %>-header">
|
||||
<button
|
||||
class="accordion-button measure-accordion-button collapsed"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#<%= data_item_section.id %>"
|
||||
aria-expanded="false"
|
||||
aria-controls="<%= data_item_section.id %>"
|
||||
>
|
||||
<%= data_item_section.title %>
|
||||
<% unless data_item_section.sufficient_data? %>
|
||||
<i class="fa-solid fa-circle-exclamation" data-exclamation-point="<%= data_item_section.id %>"></i>
|
||||
<% end %>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<div
|
||||
id="<%= data_item_section.id %>"
|
||||
class="accordion-collapse collapse"
|
||||
aria-labelledby="<%= data_item_section.id %>-header"
|
||||
data-bs-parent="#<%= data_item_section.data_item_accordion_id %>"
|
||||
>
|
||||
<div class="accordion-body measure-accordion-body font-cabin font-size-14 weight-400">
|
||||
<% unless data_item_section.sufficient_data? %>
|
||||
<div class="alert alert-secondary" role="alert" data-insufficient-data-message="<%= data_item_section.id + '-' + data_item_section.reason_for_insufficiency %>">
|
||||
Data not included due to <%= data_item_section.reason_for_insufficiency %>
|
||||
</div>
|
||||
<% end %>
|
||||
<ul>
|
||||
<% data_item_section.descriptions_and_availability.each do |data| %>
|
||||
<li><%= data.description %>
|
||||
<% unless data.available? %>
|
||||
<i class="fa-solid fa-circle-exclamation" data-missing-data="<%= data.id %>"
|
||||
data-bs-toggle="popover" data-bs-placement="right"
|
||||
data-bs-content="Data not included due to limited availability"></i>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
59
app/views/dashboard/categories/_gauge_graph.html.erb
Normal file
59
app/views/dashboard/categories/_gauge_graph.html.erb
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<div class="d-flex flex-column align-items-center position-relative">
|
||||
<% if ENV["SCORES"].present? && ENV["SCORES"].upcase == "SHOW" %>
|
||||
<p>Score is : <%= gauge.score %> </p>
|
||||
<% end %>
|
||||
|
||||
<svg
|
||||
viewBox="<%= viewbox.x %> <%= viewbox.y %> <%= viewbox.width %> <%= viewbox.height %>"
|
||||
class="<%= gauge_class %>"
|
||||
>
|
||||
<% if gauge.score_percentage.present? %>
|
||||
<path
|
||||
class="gauge-fill <%= gauge.color_class %>"
|
||||
d="<%= move_to(point: arc_start_point) %>
|
||||
<%= draw_arc(radius: outer_radius, percentage: gauge.score_percentage, clockwise: true) %>
|
||||
<%= draw_line_to(point: arc_end_line_destination(radius: inner_radius, percentage: gauge.score_percentage)) %>
|
||||
<%= draw_arc(radius: inner_radius, percentage: 0, clockwise: false) %>
|
||||
<%= draw_line_to(point: arc_end_line_destination(radius: outer_radius, percentage: 0)) %>"
|
||||
fill="none"
|
||||
stroke="none"
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
|
||||
<path
|
||||
class="gauge-outline stroke-gray-2"
|
||||
d="<%= move_to(point: arc_start_point) %>
|
||||
<%= draw_arc(radius: outer_radius, percentage: 1, clockwise: true) %>
|
||||
<%= draw_line_to(point: arc_end_line_destination(radius: inner_radius, percentage: 1)) %>
|
||||
<%= draw_arc(radius: inner_radius, percentage: 0, clockwise: false) %>
|
||||
<%= draw_line_to(point: arc_end_line_destination(radius: outer_radius, percentage: 0)) %>"
|
||||
fill="none"
|
||||
stroke-width="<%= stroke_width %>"
|
||||
/>
|
||||
|
||||
<% benchmark_boundaries = [:watch_low, :growth_low, :ideal_low]%>
|
||||
<% benchmark_boundaries.each do |zone| %>
|
||||
<line
|
||||
class="zone-benchmark stroke-gray-2"
|
||||
x1="<%= benchmark_line_point(outer_radius, angle_for(percentage: gauge.boundary_percentage_for(zone))).x %>"
|
||||
y1="<%= benchmark_line_point(outer_radius, angle_for(percentage: gauge.boundary_percentage_for(zone))).y %>"
|
||||
x2="<%= benchmark_line_point(inner_radius, angle_for(percentage: gauge.boundary_percentage_for(zone))).x %>"
|
||||
y2="<%= benchmark_line_point(inner_radius, angle_for(percentage: gauge.boundary_percentage_for(zone))).y %>"
|
||||
stroke-width="<%= stroke_width %>"
|
||||
/>
|
||||
<% end %>
|
||||
|
||||
<% if gauge.key_benchmark_percentage.present? %>
|
||||
<line
|
||||
class="zone-benchmark stroke-black"
|
||||
x1="<%= benchmark_line_point(outer_radius + 5, angle_for(percentage: gauge.key_benchmark_percentage)).x %>"
|
||||
y1="<%= benchmark_line_point(outer_radius + 5, angle_for(percentage: gauge.key_benchmark_percentage)).y %>"
|
||||
x2="<%= benchmark_line_point(inner_radius - 5 , angle_for(percentage: gauge.key_benchmark_percentage)).x %>"
|
||||
y2="<%= benchmark_line_point(inner_radius - 5, angle_for(percentage: gauge.key_benchmark_percentage)).y %>"
|
||||
stroke-width="<%= stroke_width + 2 %>"
|
||||
/>
|
||||
<% end %>
|
||||
</svg>
|
||||
<span class="gauge-title <%= font_class %> fill-black"><%= gauge.title %></span>
|
||||
</div>
|
||||
12
app/views/dashboard/categories/_measures_section.html.erb
Normal file
12
app/views/dashboard/categories/_measures_section.html.erb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div id="<%= measure_presenter.id %>" class="measure-section mx-4">
|
||||
<p class="construct-id">Measure <%= measure_presenter.id %></p>
|
||||
<h3 class="measure-description sub-header-4 mb-5 "><%= measure_presenter.name %></h3>
|
||||
<div>
|
||||
<%= render partial: "gauge_graph", locals: { gauge: measure_presenter.gauge_presenter, gauge_class: 'gauge-graph-sm', font_class: 'weight-700' } %>
|
||||
</div>
|
||||
<p class="measure-description body-small mt-5 mb-4"><%= measure_presenter.description %></p>
|
||||
|
||||
<div class="measure-accordion accordion" id="<%= measure_presenter.data_item_accordion_id %>">
|
||||
<%= render partial: "data_item_section", collection: measure_presenter.data_item_presenters %>
|
||||
</div>
|
||||
</div>
|
||||
59
app/views/dashboard/categories/_subcategory_section.html.erb
Normal file
59
app/views/dashboard/categories/_subcategory_section.html.erb
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<section class="subcategory-section">
|
||||
<div id="<%= subcategory.id %>" class="p-7">
|
||||
<p class="construct-id">Subcategory <%= subcategory.id %></p>
|
||||
<h2 class="sub-header-2 font-bitter mb-7"><%= subcategory.name %></h2>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-end">
|
||||
<div>
|
||||
<%= render partial: "gauge_graph", locals: { gauge: subcategory.gauge_presenter, gauge_class: 'gauge-graph-lg', font_class: 'sub-header-3' } %>
|
||||
</div>
|
||||
<div class="d-flex flex-column mx-7">
|
||||
<p class="body-large "><%= subcategory.description %></p>
|
||||
|
||||
<div class="d-flex justify-content-start">
|
||||
<div
|
||||
class="body-large text-center response-rate"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-trigger="hover focus"
|
||||
data-bs-content="The number of publicly available school data sources, often collected from the MA Department of Elementary and Secondary Education."
|
||||
data-bs-placement="bottom"
|
||||
>
|
||||
<p class="response-rate-percentage"><%= subcategory.admin_collection_rate.first %> / <%= subcategory.admin_collection_rate.last %></p>
|
||||
<p>school admin data sources</p>
|
||||
</div>
|
||||
<div
|
||||
class="body-large mx-3 text-center response-rate"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-trigger="hover focus"
|
||||
data-bs-content="The student survey response rate for this sub-category. This number differs from the overall response rate because each individual student receives 44 of 67 total questions, in order to avoid survey fatigue."
|
||||
data-bs-placement="bottom"
|
||||
>
|
||||
<p class="response-rate-percentage"><%= subcategory.student_response_rate %></p>
|
||||
<p>of students responded</p>
|
||||
</div>
|
||||
<div
|
||||
class="body-large text-center response-rate"
|
||||
data-bs-toggle="popover"
|
||||
data-bs-trigger="hover focus"
|
||||
data-bs-content="The teacher survey response rate for this sub-category. This number differs from the overall response rate because the survey includes skip logic to limit the number of questions for each individual survey respondent."
|
||||
data-bs-placement="bottom"
|
||||
>
|
||||
<p class="response-rate-percentage"><%= subcategory.teacher_response_rate %></p>
|
||||
<p>of teachers responded</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-container mt-7">
|
||||
<div class="arrow-shadow"></div>
|
||||
<div class="arrow"></div>
|
||||
</div>
|
||||
|
||||
<div class="measure-card d-flex p-7">
|
||||
<% subcategory.measure_presenters.each do |measure_presenter| %>
|
||||
<%= render partial: "measures_section", locals: { measure_presenter: measure_presenter } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
25
app/views/dashboard/categories/show.html.erb
Normal file
25
app/views/dashboard/categories/show.html.erb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<% content_for :navigation do %>
|
||||
<nav class="nav nav-tabs align-self-end">
|
||||
<% @categories.each do |category| %>
|
||||
<div class="nav-item">
|
||||
<%= link_to [@district, @school, category, { year: @academic_year.range }], class: ["nav-link", current_page?([@district, @school, category, { year: @academic_year.range }]) ? "active" : ""] do %>
|
||||
<i class="<%= category.icon_class %> <%= category.icon_color_class if current_page?([@district, @school, category, { year: @academic_year.range }]) %> me-2" ></i>
|
||||
<%= category.name %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</nav>
|
||||
<select id="select-academic-year" class="form-select" name="academic-year">
|
||||
<% @academic_years.each do |year| %>
|
||||
<option value="<%= url_for [@district, @school, @category , {year: year.range} ]%>" <%= @academic_year == year ? "selected" : nil %>><%= year.formatted_range %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% end %>
|
||||
<% cache [@category, @school, @academic_year] do %>
|
||||
<p class="construct-id">Category <%= @category.id %></p>
|
||||
<h1 class="sub-header font-bitter color-red"><%= @category.name %></h1>
|
||||
<p class="col-8 body-large"><%= @category.description %></p>
|
||||
<% @category.subcategories(academic_year: @academic_year, school: @school).each do |subcategory| %>
|
||||
<%= render partial: "subcategory_section", locals: {subcategory: subcategory} %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
7
config/initializers/dashboard/string_monkey_patches.rb
Normal file
7
config/initializers/dashboard/string_monkey_patches.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module StringMonkeyPatches
|
||||
def valid_likert_score?
|
||||
to_i.between? 1, 5
|
||||
end
|
||||
end
|
||||
|
||||
String.include StringMonkeyPatches
|
||||
Loading…
Add table
Add a link
Reference in a new issue