Skip to content

Commit f6d4ea5

Browse files
authored
Added days until due labels to item show and index pages (#2226)
# What it does This adds labels that let users know how long until an item is due. # Why it is important This will help folks plan their borrowing. # UI Change Screenshot index page: <img width="1045" height="812" alt="index page" src="https://github.com/user-attachments/assets/6d059d91-5d41-4637-8a24-5e586d58ce5f" /> show page with the label: <img width="605" height="366" alt="due today" src="https://github.com/user-attachments/assets/ed077701-94fc-42f4-b28f-8e4b7dc000be" /> <img width="519" height="260" alt="due tomorrow" src="https://github.com/user-attachments/assets/c6fedfea-3a31-4eb2-a494-74018a33390b" /> <img width="565" height="387" alt="due in 3 days" src="https://github.com/user-attachments/assets/cbeb53ce-0ae4-43b3-a9a9-5163ef09d9fc" /> <img width="551" height="326" alt="due in 7 days" src="https://github.com/user-attachments/assets/eacad3fb-154f-4a2c-b1d3-5d5c9f982261" /> show page without the label: <img width="582" height="422" alt="available" src="https://github.com/user-attachments/assets/3cee8d7f-db43-46e9-b4db-4f2b1d1e6406" /> # Implementation notes Feedback is always appreciated!
1 parent 01a608d commit f6d4ea5

6 files changed

Lines changed: 148 additions & 1 deletion

File tree

app/assets/stylesheets/application_styles.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,8 @@ $min-width: variables.$size-md + 1;
517517
}
518518

519519
.item-borrow-policy,
520-
.item-checkout-status {
520+
.item-checkout-status,
521+
.item-days-until-due {
521522
font-size: 0.8em;
522523
position: relative;
523524
top: -1px;

app/helpers/items_helper.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,19 @@ def item_status_label(item)
203203
tag.span label, class: "label item-checkout-status #{class_name}"
204204
end
205205

206+
def days_until_due_label(item)
207+
return unless show_days_until_due?(item)
208+
209+
days = (item.due_on - Time.zone.today).to_i
210+
text = case days
211+
when 0 then "Due today"
212+
when 1 then "Due tomorrow"
213+
else "Due in #{pluralize(days, "day")}"
214+
end
215+
216+
tag.span text, class: "label item-days-until-due"
217+
end
218+
206219
def item_holds_label(item)
207220
if item.active?
208221
count = item.active_holds.size
@@ -238,4 +251,13 @@ def item_location_span(item)
238251
def add_filter_param(param, value)
239252
(filter_params || {}).merge(param => value).sort.to_h
240253
end
254+
255+
private
256+
257+
def show_days_until_due?(item)
258+
item.checked_out_exclusive_loan.present? &&
259+
item.holdable? &&
260+
item.active_holds.empty? &&
261+
!item.overdue?
262+
end
241263
end

app/views/items/index.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
<%= tag.div class: "item-info" do %>
105105
<strong><%= link_to item.name, item_path(item, search_result_index: index), class: "item-name", id: "item-name-#{item.id}" %></strong>
106106
<%= item_status_label(item) %>
107+
<%= days_until_due_label(item) %>
107108
<% if item.borrow_policy.requires_approval? %>
108109
<span class="label label-secondary item-borrow-policy"><%= item.borrow_policy.code %>-Tool</span>
109110
<% end %>

app/views/items/show.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<h2>
1919
<strong><%= @item.complete_number %></strong>
2020
<%= item_status_label(@item) %>
21+
<%= days_until_due_label(@item) %>
2122
<% if @item.active_holds.any? %>
2223
<span class="text-small"><%= pluralize @item.active_holds.count, "hold" %></span>
2324
<% end %>

test/helpers/items_helper_test.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,76 @@ class ItemStatusTest < ItemsHelperTest
157157
end
158158
end
159159

160+
class DaysUntilDueLabelTest < ItemsHelperTest
161+
def check_out(item, due_at:)
162+
create(:loan, :checked_out, :exclusive, item: item, due_at: due_at)
163+
item.reload
164+
end
165+
166+
setup do
167+
travel_to Time.zone.local(2026, 8, 16, 10, 0, 0)
168+
end
169+
170+
test "it counts the days until an item is due" do
171+
item = create(:item)
172+
check_out(item, due_at: 3.days.from_now)
173+
174+
assert_dom_equal %(<span class="label item-days-until-due">Due in 3 days</span>), days_until_due_label(item)
175+
end
176+
177+
test "it says tomorrow for an item due in one day" do
178+
item = create(:item)
179+
check_out(item, due_at: 1.day.from_now)
180+
181+
assert_dom_equal %(<span class="label item-days-until-due">Due tomorrow</span>), days_until_due_label(item)
182+
end
183+
184+
test "it says today for an item due today" do
185+
item = create(:item)
186+
check_out(item, due_at: Time.zone.now.end_of_day)
187+
188+
assert_dom_equal %(<span class="label item-days-until-due">Due today</span>), days_until_due_label(item)
189+
end
190+
191+
test "it is nothing for an item that isn't checked out" do
192+
assert_nil days_until_due_label(create(:item))
193+
end
194+
195+
test "it is nothing for an overdue item" do
196+
item = create(:item)
197+
create(:overdue_loan, item: item)
198+
item.reload
199+
200+
assert_nil days_until_due_label(item)
201+
end
202+
203+
test "it is nothing for an item with an active hold" do
204+
item = create(:item)
205+
check_out(item, due_at: 3.days.from_now)
206+
create(:hold, :active, item: item)
207+
item.reload
208+
209+
assert_nil days_until_due_label(item)
210+
end
211+
212+
test "it is nothing for an item that can't be held" do
213+
item = create(:item, holds_enabled: false)
214+
check_out(item, due_at: 3.days.from_now)
215+
216+
assert_nil days_until_due_label(item)
217+
end
218+
219+
[:maintenance, :retired, :pending, :missing].each do |status|
220+
test "it is nothing for an item with status #{status}" do
221+
item = create(:item)
222+
check_out(item, due_at: 3.days.from_now)
223+
item.update_columns(status: Item.statuses[status])
224+
225+
assert_nil days_until_due_label(item.reload)
226+
end
227+
end
228+
end
229+
160230
class ItemStatusOptionsTest < ItemsHelperTest
161231
test "it is all item statuses and descriptions" do
162232
assert_includes item_status_options, ["Pending (just acquired; not ready to loan)", "pending"]

test/system/item_due_date_test.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
require "application_system_test_case"
2+
3+
class ItemDueDateTest < ApplicationSystemTestCase
4+
def setup
5+
@due_soon = create(:item, name: "Due Soon Drill")
6+
create(:loan, :checked_out, :exclusive, item: @due_soon, due_at: 3.days.from_now)
7+
8+
@on_hold = create(:item, name: "Spoken For Sander")
9+
create(:loan, :checked_out, :exclusive, item: @on_hold, due_at: 3.days.from_now)
10+
create(:hold, :active, item: @on_hold)
11+
12+
@overdue = create(:item, name: "Tardy Tablesaw")
13+
create(:overdue_loan, item: @overdue)
14+
end
15+
16+
test "the item list shows the days until a checked out item is due" do
17+
visit items_url
18+
19+
within("#item-#{@due_soon.id}") do
20+
assert_content "Checked Out"
21+
assert_content "Due in 3 days"
22+
end
23+
end
24+
25+
test "the item list hides the countdown for items with holds or that are overdue" do
26+
visit items_url
27+
28+
within("#item-#{@on_hold.id}") do
29+
assert_content "1 hold"
30+
refute_content "Due in"
31+
end
32+
33+
within("#item-#{@overdue.id}") do
34+
assert_content "Overdue"
35+
refute_content "Due in"
36+
end
37+
end
38+
39+
test "the item show page shows the days until the item is due" do
40+
visit item_url(@due_soon)
41+
42+
assert_content "Due in 3 days"
43+
end
44+
45+
test "the item show page hides the countdown for items with holds or that are overdue" do
46+
visit item_url(@on_hold)
47+
refute_content "Due in"
48+
49+
visit item_url(@overdue)
50+
refute_content "Due in"
51+
end
52+
end

0 commit comments

Comments
 (0)