Skip to content

Commit 2e14061

Browse files
#(feat): 6503 New case contacts table column visibility (#6945)
* feat(#6503): add Columns button and panel HTML for case contact table Adds the Columns toolbar button with a (6/6) badge, the slide-down panel with Bootstrap toggle switches for the 6 user-configurable columns, and the CaseContact::TOGGLEABLE_COLUMNS constant that drives the panel. Date and Case columns are intentionally fixed (always visible). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(#6503): wire up Columns panel toggle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(#6503): implement column visibility toggle and badge count Update View applies current toggle state and updates the (X/6) badge. Show All restores all columns, resets the badge, and closes the panel. Removes DataTables scrollX in favour of CSS overflow-x on the wrapper, which fixes header/body misalignment when columns are hidden. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(#6503): persist column visibility across page reloads Adds DataTables stateSave with server-side callbacks posting to PreferenceSet#table_state under key "case_contacts_table". The initComplete callback syncs toggle switch state and the badge count from the loaded state, since stateLoadCallback is async. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(tests): update dashboard jest tests to reflect removal of scrollX * fix: render filter panel above columns panel to match toolbar order * Make Medium not orderable (for now) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ea24660 commit 2e14061

5 files changed

Lines changed: 200 additions & 9 deletions

File tree

app/javascript/__tests__/dashboard.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,13 @@ describe('defineCaseContactsTable', () => {
6969
expect(config.searching).toBe(true)
7070
})
7171

72-
it('configures scrollX for horizontal scrolling', () => {
72+
it('disables autoWidth so columns do not expand to oversized fixed widths', () => {
7373
defineCaseContactsTable()
7474

7575
const config = mockDataTable.mock.calls[0][0]
7676

77-
expect(config.scrollX).toBe(true)
77+
expect(config.autoWidth).toBe(false)
78+
expect(config.scrollX).toBeUndefined()
7879
})
7980

8081
it('sets default sort to Date column descending', () => {
@@ -674,7 +675,7 @@ describe('defineCaseContactsTable', () => {
674675
const config = mockDataTable.mock.calls[0][0]
675676

676677
// Verify all critical config options are present
677-
expect(config).toHaveProperty('scrollX')
678+
expect(config).toHaveProperty('autoWidth')
678679
expect(config).toHaveProperty('searching')
679680
expect(config).toHaveProperty('processing')
680681
expect(config).toHaveProperty('serverSide')

app/javascript/src/dashboard.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,37 @@ function buildExpandedContent (data) {
3636

3737
const defineCaseContactsTable = function () {
3838
const table = $('table#case_contacts').DataTable({
39-
scrollX: true,
39+
autoWidth: false,
40+
stateSave: true,
41+
stateSaveCallback: function (settings, data) {
42+
$.ajax({
43+
url: '/preference_sets/table_state_update/' + settings.nTable.id + '_table',
44+
data: { table_state: JSON.stringify(data) },
45+
dataType: 'json',
46+
type: 'POST',
47+
error: function (jqXHR, textStatus, errorMessage) {
48+
console.error(errorMessage)
49+
}
50+
})
51+
},
52+
stateSaveParams: function (settings, data) {
53+
data.search.search = ''
54+
return data
55+
},
56+
stateLoadCallback: function (settings, callback) {
57+
$.ajax({
58+
url: '/preference_sets/table_state/' + settings.nTable.id + '_table',
59+
dataType: 'json',
60+
type: 'GET',
61+
success: function (json) { callback(json) }
62+
})
63+
},
64+
initComplete: function () {
65+
$('.cc-column-toggle').each(function () {
66+
$(this).prop('checked', table.column($(this).data('column-index')).visible())
67+
})
68+
updateColumnsButtonBadge()
69+
},
4070
searching: true,
4171
processing: true,
4272
serverSide: true,
@@ -99,6 +129,7 @@ const defineCaseContactsTable = function () {
99129
},
100130
{ // Medium column (index 5)
101131
data: 'medium_type',
132+
orderable: false,
102133
render: (data) => data || ''
103134
},
104135
{ // Created By column (index 6)
@@ -256,6 +287,33 @@ const defineCaseContactsTable = function () {
256287
})
257288
})
258289

290+
function updateColumnsButtonBadge () {
291+
const total = $('.cc-column-toggle').length
292+
const visible = $('.cc-column-toggle:checked').length
293+
$('#cc-columns-count').text(`(${visible}/${total})`).css('visibility', 'visible')
294+
}
295+
296+
$('#cc-columns-toggle').on('click', function () {
297+
$('#cc-columns-panel').toggle()
298+
})
299+
300+
$('#cc-columns-update').on('click', function () {
301+
$('.cc-column-toggle').each(function () {
302+
table.column($(this).data('column-index')).visible($(this).is(':checked'))
303+
})
304+
updateColumnsButtonBadge()
305+
$('#cc-columns-panel').hide()
306+
})
307+
308+
$('#cc-columns-show-all').on('click', function () {
309+
$('.cc-column-toggle').prop('checked', true)
310+
$('.cc-column-toggle').each(function () {
311+
table.column($(this).data('column-index')).visible(true)
312+
})
313+
updateColumnsButtonBadge()
314+
$('#cc-columns-panel').hide()
315+
})
316+
259317
$('#cc-filter-toggle').on('click', function () {
260318
$('#cc-filter-panel').toggle()
261319
})

app/models/case_contact.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ def active_or_notes?
191191
LETTER = "letter".freeze
192192
CONTACT_MEDIUMS = [IN_PERSON, TEXT_EMAIL, VIDEO, VOICE_ONLY, LETTER].freeze
193193

194+
TOGGLEABLE_COLUMNS = [
195+
{key: "relationship", label: "Relationship", index: 4},
196+
{key: "medium", label: "Medium", index: 5},
197+
{key: "created_by", label: "Created By", index: 6},
198+
{key: "contacted", label: "Contacted", index: 7},
199+
{key: "topics", label: "Topics", index: 8},
200+
{key: "draft", label: "Draft", index: 9}
201+
].freeze
202+
194203
def update_cleaning_contact_types(args)
195204
transaction do
196205
contact_types.clear

app/views/case_contacts/case_contacts_new_design/index.html.erb

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22
<h1 class="mb-20">Case Contacts</h1>
33

44
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3" id="cc-filter-toolbar">
5-
<button type="button" id="cc-filter-toggle" class="main-btn secondary-btn btn-sm btn-hover">
6-
<i class="lni lni-funnel mr-5" aria-hidden="true"></i>
7-
Filter
8-
</button>
5+
<div class="d-flex gap-2">
6+
<button type="button" id="cc-filter-toggle" class="main-btn secondary-btn btn-sm btn-hover">
7+
<i class="lni lni-funnel mr-5" aria-hidden="true"></i>
8+
Filter
9+
</button>
10+
11+
<button type="button" id="cc-columns-toggle" class="main-btn secondary-btn btn-sm btn-hover">
12+
<i class="lni lni-list mr-5" aria-hidden="true"></i>
13+
Columns <span id="cc-columns-count" style="visibility: hidden">(<%= CaseContact::TOGGLEABLE_COLUMNS.length %>/<%= CaseContact::TOGGLEABLE_COLUMNS.length %>)</span>
14+
</button>
15+
</div>
916

1017
<%= link_to new_case_contact_path, class: "main-btn primary-btn btn-sm btn-hover" do %>
1118
<i class="lni lni-plus mr-10" aria-hidden="true"></i>
@@ -100,10 +107,36 @@
100107
</div>
101108
</div>
102109
</div>
110+
111+
<div id="cc-columns-panel" class="card-style mb-3" style="display: none;">
112+
<div class="card-content">
113+
<div class="row mb-3">
114+
<% CaseContact::TOGGLEABLE_COLUMNS.each do |col| %>
115+
<div class="col-md-4">
116+
<div class="form-check form-switch">
117+
<input type="checkbox"
118+
class="form-check-input cc-column-toggle"
119+
id="cc-col-<%= col[:key] %>"
120+
data-column-index="<%= col[:index] %>"
121+
role="switch"
122+
checked>
123+
<label class="form-check-label" for="cc-col-<%= col[:key] %>">
124+
<%= col[:label] %>
125+
</label>
126+
</div>
127+
</div>
128+
<% end %>
129+
</div>
130+
<div class="d-flex gap-2">
131+
<button type="button" id="cc-columns-update" class="main-btn primary-btn btn-sm btn-hover">Update View</button>
132+
<button type="button" id="cc-columns-show-all" class="main-btn dark-btn-outline btn-sm btn-hover">Show All</button>
133+
</div>
134+
</div>
135+
</div>
103136
</div>
104137

105138
<div class="card-style mb-30">
106-
<div class="table-wrapper">
139+
<div class="table-wrapper" style="overflow-x: auto">
107140
<table
108141
id="case_contacts"
109142
class="table"

spec/system/case_contacts/case_contacts_new_design_spec.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,96 @@
2525
end
2626
end
2727

28+
describe "columns panel" do
29+
include_context "signed in as admin"
30+
31+
it "shows a Columns button in the toolbar" do
32+
expect(page).to have_button("Columns")
33+
end
34+
35+
it "shows a visible count badge on the Columns button" do
36+
expect(page).to have_button(text: /Columns\s*\(6\/6\)/)
37+
end
38+
39+
it "hides the columns panel by default" do
40+
expect(page).not_to have_css("#cc-columns-panel", visible: true)
41+
end
42+
43+
it "opens the columns panel when the Columns button is clicked" do
44+
click_button "Columns"
45+
expect(page).to have_css("#cc-columns-panel", visible: true)
46+
end
47+
48+
it "closes the columns panel when the Columns button is clicked again" do
49+
click_button "Columns"
50+
click_button "Columns"
51+
expect(page).not_to have_css("#cc-columns-panel", visible: true)
52+
end
53+
54+
it "hides a column when its toggle is switched off and Update View is clicked" do
55+
click_button "Columns"
56+
uncheck "Medium"
57+
click_button "Update View"
58+
59+
expect(page).not_to have_css("th", text: "Medium")
60+
end
61+
62+
it "shows a column again when its toggle is switched back on and Update View is clicked" do
63+
click_button "Columns"
64+
uncheck "Medium"
65+
click_button "Update View"
66+
67+
click_button "Columns"
68+
check "Medium"
69+
click_button "Update View"
70+
71+
expect(page).to have_css("th", text: "Medium")
72+
end
73+
74+
it "updates the badge count when a column is hidden" do
75+
click_button "Columns"
76+
uncheck "Medium"
77+
click_button "Update View"
78+
79+
expect(page).to have_button(text: /Columns\s*\(5\/6\)/)
80+
end
81+
82+
it "shows all columns and closes the panel when Show All is clicked" do
83+
click_button "Columns"
84+
uncheck "Medium"
85+
uncheck "Topics"
86+
click_button "Update View"
87+
88+
click_button "Columns"
89+
click_button "Show All"
90+
91+
expect(page).to have_css("th", text: "Medium")
92+
expect(page).to have_css("th", text: "Topics")
93+
expect(page).to have_button(text: /Columns\s*\(6\/6\)/)
94+
expect(page).not_to have_css("#cc-columns-panel", visible: true)
95+
end
96+
97+
it "persists hidden columns across page reloads" do
98+
click_button "Columns"
99+
uncheck "Medium"
100+
click_button "Update View"
101+
102+
visit case_contacts_new_design_path
103+
104+
expect(page).not_to have_css("th", text: "Medium")
105+
expect(page).to have_button(text: /Columns\s*\(5\/6\)/)
106+
end
107+
108+
it "lists all 6 toggleable columns with toggle switches, all on by default" do
109+
%w[Relationship Medium Contacted Topics Draft].each do |label|
110+
expect(page).to have_css("#cc-columns-panel .form-switch", text: label, visible: :all)
111+
expect(page).to have_field(label, checked: true, visible: :all)
112+
end
113+
expect(page).to have_css("#cc-columns-panel .form-switch", text: "Created By", visible: :all)
114+
expect(page).to have_field("Created By", checked: true, visible: :all)
115+
end
116+
end
117+
28118
describe "filter panel" do
29119
let!(:in_person_contact) do
30120
create(:case_contact, :active, casa_case: casa_case,

0 commit comments

Comments
 (0)