Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bbd2396
Add Tailwind CSS v4 + redesign sign-in page (casadesign)
Jul 9, 2026
51b21e4
Use Figtree instead of Inter across the design system
Jul 9, 2026
d48c24a
Add design.md design system + record casadesign workflow
Jul 9, 2026
4edf54b
Add Tailwind app-shell layout (casa_app)
Jul 9, 2026
898942d
Redesign supervisor landing as a triage dashboard
Jul 9, 2026
15e4cac
Refine supervisor dashboard: initials, page title, WCAG + responsive
Jul 9, 2026
3409005
Show user names without honorific prefixes (display_person helper)
Jul 9, 2026
5d22005
Remove redundant sidebar identity block
Jul 9, 2026
a238ced
Drop the sidebar logo mark — org name only
Jul 9, 2026
b3cc06e
Redesign remaining auth pages onto the Tailwind casa_auth layout
Jul 9, 2026
82e1eb1
Strip honorifics from names across legacy pages + datatables
Jul 9, 2026
2ade255
Shorten dashboard subtitle (drop em-dash and redundant org name)
Jul 9, 2026
8cb66a6
test(dashboard): expect supervisor dashboard to render, not redirect
Jul 10, 2026
51024ba
feat(notifications): migrate notifications page to the Tailwind casa_…
Jul 10, 2026
c9e0091
docs(design): make design.md the permanent design-system record + add…
Jul 10, 2026
1c0e204
feat(profile): migrate edit-profile page to the Tailwind casa_app shell
Jul 10, 2026
943b0d9
docs(design): record Form errors + Disclosure components; mark edit p…
Jul 10, 2026
2111aef
feat(shell): show the user's role in the account menu; refine dashboa…
Jul 10, 2026
867d38f
feat(shell): drop icon from role badge and colour-code it by role
Jul 10, 2026
2d9d4c6
refactor(design): sentence-case labels, streamline error summary, ado…
Jul 10, 2026
1235e35
refactor(profile): collapse form-error summary to one streamlined line
Jul 10, 2026
8ec4fa1
feat(dashboard): add the volunteer landing dashboard
Jul 10, 2026
28c2226
feat(dashboard): add the admin org-wide dashboard
Jul 10, 2026
e5e74dd
style(dashboard): give the roster/cases table cards a bottom inset
Jul 10, 2026
88a2759
style(notifications): inset the list card so rows clear the rounded c…
Jul 10, 2026
05dfa2c
feat(design): datatable strategy — theme jQuery DataTables via a Tail…
Jul 10, 2026
20d29ed
feat(cases): migrate the cases index onto the shell + DataTables skin
Jul 10, 2026
94f7dfb
refactor(cases): rebuild the cases index as a bespoke table (retire D…
Jul 10, 2026
cb7a76c
fix(cases): refine the bespoke table — filter chevrons, row action, t…
Jul 10, 2026
6152877
fix(cases): label the row Edit action (icon + text)
Jul 10, 2026
0968d76
feat(cases): replace dead hearing/judge columns with "Next court date"
Jul 10, 2026
1f65a73
fix(cases): make the row Edit action a tertiary (ghost) button
Jul 10, 2026
3eec6bd
feat(cases): collapse secondary header actions into a "More" menu
Jul 13, 2026
52ca1c1
feat(health): rebuild the metrics page as accessible server-rendered …
Jul 13, 2026
2f66b3a
feat(health): add a date-range filter and a Stimulus hover layer
Jul 13, 2026
660473f
chore(deps): drop unused chart.js, chartjs-adapter-luxon, and luxon
Jul 13, 2026
ddca724
fix(shell): open only one header dropdown at a time
Jul 13, 2026
8fca6d3
feat(cases): sortable column headers
Jul 13, 2026
f87fc55
feat(cases): free-text search on the cases index
Jul 13, 2026
24e19c9
feat(cases): responsive cases index (mobile cards + 2-up filters)
Jul 13, 2026
fad6e19
feat(health): responsive metrics page
Jul 13, 2026
05ed2cf
feat(dashboards): responsive supervisor + volunteer tables
Jul 14, 2026
e53aded
Fix WCAG AA contrast on the swept pages (notifications, edit profile,…
Jul 14, 2026
55396c8
Record the responsive sweep and true-width verification in the design…
Jul 14, 2026
c952c86
Add bin/measure-responsive.mjs, the true-width overflow checker
Jul 14, 2026
f7175b2
Harden shipped work: fix occurred_at flake, cover SupervisorDashboard
Jul 14, 2026
0d3087c
Case show infra: add-to-calendar and modal Stimulus controllers
Jul 14, 2026
a5642e1
Case show infra: Tailwind case-contact card + medium_icon decorator
Jul 14, 2026
bc8db2a
Case show infra: court-report and local-storage-reset controllers
Jul 14, 2026
7cea364
Migrate Case show to the Tailwind casa_app layout (Phase B)
Jul 14, 2026
21aeade
Fix WCAG AA contrast on the Resolve Reminder button
Jul 14, 2026
a6a61e6
Docs: record the Case show migration + native-dialog modal pattern
Jul 14, 2026
3b4c18d
Migrate case groups to Tailwind and add button + modal design tokens
Jul 14, 2026
fba5d3f
Extract the native-dialog modal into a Dialog:: component suite
Jul 14, 2026
dde15a9
Collapse occasional case actions into a More menu
Jul 14, 2026
3be2a11
Move Emancipation into the case-show More menu
Jul 14, 2026
900219e
Collapse case-show secondary actions into More on mobile
Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ Hotwire-first: Turbo for navigation/forms, Stimulus controllers in `app/javascri
## Style
This project uses **Standard.rb** (not vanilla RuboCop). Don't fight it on spacing/quotes/trailing commas. There is also a `.standard_todo.yml` of grandfathered violations — leave older files alone unless touching them substantively. Older migration files (2020–2024) are intentionally excluded from linting.

## Design system (casadesign branch)

This branch introduces a Tailwind CSS v4 design system, documented in **`design.md`** (repo root) — the source of truth for the new aesthetic. Read it before UI work and keep it updated. New pages use Tailwind-only layouts and coexist with the legacy Bootstrap UI (migrate page-by-page). **On every page, show user names without honorific prefixes (first + last only) — `display_person` for new UI, `formatted_name` at existing `.display_name` sites — never mutate the stored `display_name`.** **Workflow: on `casadesign`, commit and push to the `casadesign` branch at every checkpoint.** **Buttons come from the `button_classes(:primary|:secondary|:danger)` helper (one shared 40px height token, never hand-written class strings). Native-dialog modals use the `Dialog::` ViewComponent suite (`Dialog::GroupComponent` + `Dialog::HeaderComponent`/`BodyComponent`/`FooterComponent`), not the legacy Bootstrap `Modal::*`. Page headers use one primary CTA plus a `More` overflow menu (native `<details>` + the `dropdown` controller): keep core actions visible and overflow only occasional ones, never bury a core action (also breaks non-JS specs, which can't open `<details>`). On mobile, collapse the remaining visible secondaries into `More` too (render twice with `hidden sm:contents` / `sm:hidden`) so only the primary CTA and `More` share the top line. Leading icons on menu/list items top-align to the first line (`items-start`), never centered against wrapped text.**

## Where to look for more
- `.github/instructions/ruby.instructions.md` and `.github/instructions/copilot-review.instructions.md` — the project's own review checklist; the source of truth for "what is a bug-shaped change here."
- `doc/architecture-decisions/` — ADRs (especially 0002 no-UI-signup, 0003 two-user-tables, 0006 few-controller-tests, 0007 inline-email-CSS).
Expand Down
1 change: 1 addition & 0 deletions Procfile.dev
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
web: bin/rails server -p 3000 -b 0.0.0.0
js: npm run build:dev --watch
css: npm run build:css:dev --watch
tw: npm run build:tailwind:dev
91 changes: 91 additions & 0 deletions app/assets/stylesheets/tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@import "tailwindcss";
@import "tom-select/dist/css/tom-select.css";

/* Where Tailwind should look for class names (paths are relative to this file). */
@source "../../components/**/*.{erb,rb}";
@source "../../views/**/*.erb";
@source "../../helpers/**/*.rb";
@source "../../javascript/**/*.js";

/* ------------------------------------------------------------------
CASA design system (Tailwind v4 CSS-first config).
A calm, professional indigo palette + Figtree — the visual direction
for the casadesign redesign.
------------------------------------------------------------------ */
@theme {
--font-sans: "Figtree", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

--color-brand-50: #eef2ff;
--color-brand-100: #e0e7ff;
--color-brand-200: #c7d2fe;
--color-brand-300: #a5b4fc;
--color-brand-400: #818cf8;
--color-brand-500: #6366f1;
--color-brand-600: #4f46e5;
--color-brand-700: #4338ca;
--color-brand-800: #3730a3;
--color-brand-900: #312e81;
}

/* ------------------------------------------------------------------
TomSelect (the `multiple-select` Stimulus controller) restyled to the
design system. tom-select.css above provides the structure; these rules
theme it to match our inputs (slate border, rounded-lg, brand focus +
brand-50 pills). Only loaded on casa_app; Bootstrap pages keep the
tom-select.bootstrap5 theme from application.scss.
------------------------------------------------------------------ */
.ts-wrapper { font-size: 0.875rem; }
.ts-control {
border-radius: 0.5rem;
border: 1px solid #cbd5e1; /* slate-300 */
padding: 0.375rem 0.5rem;
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
background-color: #fff;
gap: 0.375rem;
}
.ts-wrapper.focus .ts-control {
border-color: #6366f1; /* brand-500 */
box-shadow: 0 0 0 2px rgb(99 102 241 / 0.3);
}
.ts-control > .item {
border-radius: 9999px;
background-color: #eef2ff; /* brand-50 */
color: #4338ca; /* brand-700 */
padding: 0.125rem 0.625rem;
font-weight: 500;
border: none;
}
.ts-control > .item .remove {
border-left: none;
color: #6366f1;
padding: 0 0.35rem;
}
.ts-control > .item .remove:hover { background: transparent; color: #4338ca; }
.ts-control input::placeholder { color: #64748b; } /* slate-500 */
.ts-dropdown {
border-radius: 0.5rem;
border: 1px solid #e2e8f0; /* slate-200 */
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
margin-top: 0.25rem;
}
.ts-dropdown .option { padding: 0.5rem 0.75rem; color: #334155; } /* slate-700 */
.ts-dropdown .active { background-color: #eef2ff; color: #4338ca; } /* brand-50 / brand-700 */

/* ------------------------------------------------------------------
Native <dialog> modals (the `modal` Stimulus controller). Tailwind's reset
zeroes the `margin: auto` the UA uses to center a modal dialog, which pins it
to the top-left. Position it explicitly: horizontally centered and sitting
about a third of the way down, capped so tall modals stay on screen.
------------------------------------------------------------------ */
dialog[data-modal-target="dialog"] {
position: fixed;
left: 50%;
top: 24vh;
transform: translateX(-50%);
margin: 0;
max-height: 80vh;
overflow-y: auto;
}
@media (max-width: 640px) {
dialog[data-modal-target="dialog"] { top: 18vh; }
}
1 change: 1 addition & 0 deletions app/components/dialog/body_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="<%= body_classes %>"><%= content %></div>
16 changes: 16 additions & 0 deletions app/components/dialog/body_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

# The body region of a design-system dialog. `centered` switches to the status
# layout (centered hero badge + title + text). Pass extra `classes` (e.g.
# "space-y-4") for form bodies with several stacked fields.
class Dialog::BodyComponent < ViewComponent::Base
def initialize(centered: false, classes: nil)
@centered = centered
@classes = classes
end

def body_classes
base = @centered ? "px-5 pt-6 pb-4 text-center" : "px-5 py-4"
"#{base} #{@classes}".strip
end
end
1 change: 1 addition & 0 deletions app/components/dialog/footer_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="<%= footer_classes %>"><%= content %></div>
15 changes: 15 additions & 0 deletions app/components/dialog/footer_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

# The footer region of a design-system dialog: a divided action row. Actions are
# right-aligned by default (`:end`); use `:center` for a single-action status modal.
# Build the actions with the `button_classes` helper.
class Dialog::FooterComponent < ViewComponent::Base
def initialize(align: :end)
@align = align
end

def footer_classes
justify = (@align == :center) ? "justify-center" : "justify-end"
"flex items-center #{justify} gap-2 border-t border-slate-100 px-5 py-4"
end
end
6 changes: 6 additions & 0 deletions app/components/dialog/group_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= tag.div class: @wrapper_class, data: wrapper_data do %>
<%= trigger %>
<%= tag.dialog id: @id, class: panel_classes, aria: {label: @label}, data: {modal_target: "dialog", action: "click->modal#backdropClose"} do %>
<%= content %>
<% end %>
<% end %>
41 changes: 41 additions & 0 deletions app/components/dialog/group_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

# The native-<dialog> shell for the design-system modal. Pairs with the `modal`
# Stimulus controller (`showModal()` gives a focus trap, Escape-to-close, and an
# inert background for free). Compose the inside from Dialog::Header / Dialog::Body
# / Dialog::Footer so the template cannot drift. This is the Tailwind replacement
# for the legacy Bootstrap Modal::* suite; do not restyle Bootstrap `.modal` markup.
class Dialog::GroupComponent < ViewComponent::Base
renders_one :trigger

SIZES = {sm: "max-w-sm", md: "max-w-md", lg: "max-w-lg"}.freeze

# size: panel max-width (:sm | :md | :lg)
# label: accessible name for the dialog (usually the title text)
# id: optional id on the <dialog> (e.g. a JS / test hook)
# open_on_connect: auto-open on connect via the modal controller
# controllers: extra Stimulus controllers on the wrapper (e.g. "court-report")
# data: extra wrapper data attributes (e.g. local-storage-reset-key-value)
# wrapper_class: layout for the wrapper div; default "inline-flex". Use "contents"
# to dissolve the wrapper so the trigger and dialog sit directly in
# a parent such as a dropdown menu (the trigger becomes a menu item).
def initialize(size: :md, label: nil, id: nil, open_on_connect: false, controllers: nil, data: {}, wrapper_class: "inline-flex")
@size = size
@label = label
@id = id
@open_on_connect = open_on_connect
@controllers = controllers
@data = data
@wrapper_class = wrapper_class
end

def wrapper_data
merged = @data.merge(controller: ["modal", @controllers].compact.join(" ").strip)
merged[:modal_open_on_connect_value] = true if @open_on_connect
merged
end

def panel_classes
"w-[calc(100vw-2rem)] #{SIZES.fetch(@size)} overflow-hidden rounded-2xl p-0 text-left shadow-xl backdrop:bg-slate-900/40"
end
end
9 changes: 9 additions & 0 deletions app/components/dialog/header_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="flex items-center gap-3 border-b border-slate-100 px-5 py-4">
<% if @icon %><span class="<%= badge_classes %>"><i class="<%= @icon %>" aria-hidden="true"></i></span><% end %>
<h2 class="min-w-0 flex-1 text-base font-semibold text-slate-900"><%= @title %></h2>
<% if @closable %>
<button type="button" data-action="modal#close" aria-label="Close" class="grid h-8 w-8 shrink-0 place-items-center rounded-lg text-slate-500 hover:bg-slate-100 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500">
<i class="bi bi-x-lg" aria-hidden="true"></i>
</button>
<% end %>
</div>
27 changes: 27 additions & 0 deletions app/components/dialog/header_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

# The header row of a design-system dialog: an optional status badge, the title,
# and a close button. Render it inside Dialog::GroupComponent (it may sit inside a
# form for form-driven modals).
class Dialog::HeaderComponent < ViewComponent::Base
BADGE = {
danger: "bg-rose-100 text-rose-600",
success: "bg-emerald-50 text-emerald-600",
info: "bg-brand-50 text-brand-600"
}.freeze

# title: heading text
# icon: optional bi-* class for a 32px status badge
# variant: badge color when an icon is shown (:danger | :success | :info)
# closable: render the close (X) button (default true)
def initialize(title:, icon: nil, variant: :info, closable: true)
@title = title
@icon = icon
@variant = variant
@closable = closable
end

def badge_classes
"grid h-8 w-8 shrink-0 place-items-center rounded-full #{BADGE.fetch(@variant, BADGE[:info])}"
end
end
33 changes: 17 additions & 16 deletions app/components/notification_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<% read = notification.read? %>
<a
href="<%= mark_as_read_notification_path(notification) %>"
class="<%= muted_display %> list-group-item list-group-item-action flex-column align-items-start"
data-method="post">
<div class="d-flex flex-row">
<div class="d-flex-shrink-0">
<% unless notification.read? %>
<i class='fas fa-bell'></i>
<% end %>
</div>
<div class="ml-2 flex-grow-1">
<div class="d-flex justify-content-between">
<h5 class="mb-1"><%= notification.event.title %></h5>
<small><%= time_ago_in_words(notification.created_at) %> ago</small>
</div>
<div class="my-1">
<%= simple_format notification.event.message %>
</div>
data-method="post"
data-notification-list-item
<%= "data-read" if read %>
class="group flex items-start gap-3.5 px-4 py-3.5 transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500">
<span class="grid h-9 w-9 shrink-0 place-items-center rounded-xl <%= read ? "bg-slate-100 text-slate-400" : "bg-brand-50 text-brand-600" %>">
<i class="bi <%= read ? "bi-bell" : "bi-bell-fill" %> text-sm" aria-hidden="true"></i>
</span>
<div class="min-w-0 flex-1">
<div class="flex items-baseline justify-between gap-3">
<p class="truncate text-sm font-semibold <%= read ? "text-slate-500" : "text-slate-900" %>"><%= notification.event.title %></p>
<span class="shrink-0 text-xs text-slate-500"><%= time_ago_in_words(notification.created_at) %> ago</span>
</div>
<div class="mt-0.5 text-sm text-slate-600"><%= simple_format notification.event.message %></div>
</div>
<% unless read %>
<span data-unread-dot class="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-brand-500" aria-hidden="true"></span>
<span class="sr-only">Unread</span>
<% end %>
</a>
4 changes: 0 additions & 4 deletions app/components/notification_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,4 @@ class NotificationComponent < ViewComponent::Base
def initialize(notification:)
@notification = notification
end

def muted_display
"bg-light text-muted" if notification.read?
end
end
1 change: 1 addition & 0 deletions app/controllers/all_casa_admins/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

class AllCasaAdmins::SessionsController < Devise::SessionsController
include Accessible
layout "casa_auth"
skip_before_action :check_user, only: :destroy
end
75 changes: 70 additions & 5 deletions app/controllers/casa_cases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@ class CasaCasesController < ApplicationController
before_action :require_organization!
after_action :verify_authorized

SORT_COLUMNS = %w[case_number next_court_date status transition assigned].freeze

def index
authorize CasaCase
org_cases = current_user.casa_org.casa_cases.includes(:assigned_volunteers, :casa_case_emancipation_categories)
@casa_cases = policy_scope(org_cases).includes([:hearing_type, :judge])
@casa_cases_filter_id = policy(CasaCase).can_see_filters? ? "casa-cases" : ""
@duties = OtherDuty.where(creator_id: current_user.id)
@active_nav = "cases"
@sort = SORT_COLUMNS.include?(params[:sort]) ? params[:sort] : "case_number"
@direction = (params[:direction] == "desc") ? "desc" : "asc"
org_cases = current_user.casa_org.casa_cases.includes(:assigned_volunteers, :court_dates)
scope = policy_scope(org_cases)
scope = filter_casa_cases(scope) if policy(CasaCase).can_see_filters?
@pagy, @casa_cases = pagy(order_casa_cases(scope))
render :index, layout: "casa_app"
end

def show
authorize @casa_case
@active_nav = "cases"

respond_to do |format|
format.html {}
format.html { render layout: "casa_app" }
format.csv do
case_contacts = @casa_case.decorate.case_contacts_ordered_by_occurred_at
csv = CaseContactsExportCsvService.new(case_contacts, CaseContactReport::COLUMNS).perform
Expand Down Expand Up @@ -140,6 +147,64 @@ def copy_court_orders

private

# Orders the cases index by the whitelisted ?sort= column and ?direction=. Derived
# columns (next court date, assigned volunteer) use correlated subqueries; a secondary
# sort by case number keeps pagination stable.
def order_casa_cases(scope)
today = ActiveRecord::Base.connection.quote(Date.current)
clause =
case @sort
when "status" then "casa_cases.active"
when "transition" then "casa_cases.birth_month_year_youth"
when "next_court_date"
"(SELECT MIN(court_dates.date) FROM court_dates WHERE court_dates.casa_case_id = casa_cases.id AND court_dates.date >= #{today})"
when "assigned"
"(SELECT MIN(users.display_name) FROM case_assignments JOIN users ON users.id = case_assignments.volunteer_id WHERE case_assignments.casa_case_id = casa_cases.id AND case_assignments.active)"
else "casa_cases.case_number"
end
scope = scope.order(Arel.sql("#{clause} #{@direction} NULLS LAST"))
scope = scope.order(case_number: :asc) unless @sort == "case_number"
scope
end

# Server-side filtering for the cases index (admins/supervisors). Params come from the
# filter bar selects; volunteers never reach this. Status defaults to active.
def filter_casa_cases(scope)
if params[:search].present?
term = "%#{ActiveRecord::Base.sanitize_sql_like(params[:search].strip)}%"
scope = scope.where(
"casa_cases.case_number ILIKE :term OR EXISTS (SELECT 1 FROM case_assignments ca " \
"JOIN users u ON u.id = ca.volunteer_id WHERE ca.casa_case_id = casa_cases.id AND ca.active " \
"AND u.display_name ILIKE :term)",
term: term
)
end

scope = case params[:status]
when "inactive" then scope.inactive
when "all" then scope
else scope.active
end

case params[:assigned]
when "assigned" then scope = scope.where(id: CaseAssignment.active.select(:casa_case_id))
when "unassigned" then scope = scope.where.not(id: CaseAssignment.active.select(:casa_case_id))
end

case params[:transition]
when "yes" then scope = scope.is_transitioned
when "no" then scope = scope.where.not(id: current_user.casa_org.casa_cases.is_transitioned.select(:id))
end

case params[:prefix]
when "CINA" then scope = scope.where("case_number ILIKE ?", "CINA%")
when "TPR" then scope = scope.where("case_number ILIKE ?", "TPR%")
when "None" then scope = scope.where.not("case_number ILIKE ? OR case_number ILIKE ?", "CINA%", "TPR%")
end

scope
end

# Use callbacks to share common setup or constraints between actions.
def set_casa_case
@casa_case = current_organization.casa_cases.friendly.find(params[:id])
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/case_groups_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class CaseGroupsController < ApplicationController
layout "casa_app"
before_action :require_organization!
before_action -> { @active_nav = "cases" }
before_action :set_case_group, only: %i[edit update destroy]

def index
Expand Down
Loading
Loading