Colour progress bars by how the planting is getting on

Blue while growing, green while harvesting, amber when late, red when very
late, grey when finished. The track is now neutral grey so every colour
reads against it. Planting#progress_state decides, and the garden cards,
planting progress and timeline bars use it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Brenda WallaceandClaude Sonnet 5 committed 2026-09-20 21:08:52 +12:00
1 parent b7f4758a5b
commit a05df8546f
9 files changed
+66 -8

No files matched your search

+3 -3
View File
@@ -111,8 +111,8 @@ $btn-font-weight: 500;
$btn-box-shadow: $material-shadow-raised;
$btn-active-box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 3px rgba(0, 0, 0, 0.12), 0 4px 15px 0 rgba(0, 0, 0, 0.2);
// Progress bars: a thin strip, dark green filled over a lighter green track, as before.
// (These were Bootstrap 3 variables the old skin ignored; Bootstrap 5 reads them.)
// Progress bars: a thin strip over a neutral track. The bar's colour shows how the planting is
// getting on (see .progress-bar--* in overrides.scss).
$progress-height: 0.875rem;
$progress-bg: lighten($green, 15%);
$progress-bg: #e0e0e0;
$progress-bar-bg: $green;
+22
View File
@@ -456,6 +456,7 @@ ul.thumbnail-buttons {
text-align: justify;
}
// Buttons are uppercase, as in the old skin, except link buttons.
// Bootstrap only draws the button shadow when $enable-shadows is on, so draw it here.
.btn {
box-shadow: var(--bs-btn-box-shadow);
@@ -486,6 +487,27 @@ ul.thumbnail-buttons {
--bs-btn-active-bg: rgba(0, 0, 0, 0.12);
}
// Progress bar colours by state (Planting#progress_state).
.progress-bar--growing {
background-color: var(--bs-info);
}
.progress-bar--harvesting {
background-color: var(--bs-success);
}
.progress-bar--late {
background-color: var(--bs-warning);
}
.progress-bar--super_late {
background-color: var(--bs-danger);
}
.progress-bar--finished {
background-color: var(--bs-secondary);
}
// The card variables drop the card's border, which also drew the line under a
// header, so put that line back.
.card-header {
+3 -3
View File
@@ -29,7 +29,7 @@ export default function PlantingRow({planting, defaultIconUrl, highlighted}) {
{planting.progress_note ? (
<small className="planting-row-note">{planting.progress_note}</small>
) : (
<Progress percentage={planting.percentage_grown} finishLabel={planting.finish_predicted_label} />
<Progress percentage={planting.percentage_grown} state={planting.progress_state} finishLabel={planting.finish_predicted_label} />
)}
</div>
<div className="planting-row-actions">
@@ -45,14 +45,14 @@ export default function PlantingRow({planting, defaultIconUrl, highlighted}) {
);
}
function Progress({percentage, finishLabel}) {
function Progress({percentage, state, finishLabel}) {
if (percentage === null || percentage === undefined) return null;
return (
<div className="planting-progress">
<div className="progress">
<div
className="progress-bar bg-success"
className={`progress-bar progress-bar--${state}`}
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
+15
View File
@@ -77,6 +77,21 @@ module PredictPlanting
crop.annual? && planted_at.present? && finish_predicted_at.present?
end
# How the planting is getting on, for colouring its progress bar.
def progress_state
if finished?
:finished
elsif super_late?
:super_late
elsif late?
:late
elsif harvest_time?
:harvesting
else
:growing
end
end
# Planting has live more then 90 days past predicted finish
def super_late?
late? && (finish_predicted_at + 90.days) < Time.zone.today
@@ -120,6 +120,7 @@ class GardenCardSerializer
{
id: planting.id, url: routes.planting_path(planting), crop: crop_chip(planting),
planted_at: planting.planted_at, percentage_grown: planting.percentage_grown,
progress_state: planting.progress_state,
finish_predicted_label: finish_label(planting), progress_note: progress_note(planting),
badges: badges(planting), actions: planting_actions(planting)
}
+1 -1
View File
@@ -1,6 +1,6 @@
- if planting.active && planting.annual? && planting.percentage_grown.present?
.progress
.progress-bar.bg-success{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, role: "progressbar", style: "width: #{planting.percentage_grown}%"}
.progress-bar{class: "progress-bar--#{planting.progress_state}", "aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, role: "progressbar", style: "width: #{planting.percentage_grown}%"}
.float-start #{sprintf '%.0f', planting.percentage_grown}%
+1 -1
View File
@@ -14,7 +14,7 @@
%p.small #{finished_icon} Finish expected #{I18n.l planting.finish_predicted_at}
- if planting.planted_at.present? && planting.expected_lifespan.present?
.progress
.progress-bar{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, role: "progressbar", style: "width: #{planting.percentage_grown}%"}
.progress-bar{class: "progress-bar--#{planting.progress_state}", "aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, role: "progressbar", style: "width: #{planting.percentage_grown}%"}
%ul.list-unstyled.d-flex.justify-content-between
- in_weeks(planting.expected_lifespan).times do |week_number|
%li{class: planting.planted_at + week_number.weeks > Time.zone.today ? 'text-muted progress-fade' : '', 'data-bs-toggle': "tooltip", 'data-bs-placement': "top", title: I18n.l(planting.planted_at + week_number.weeks)}
+16
View File
@@ -113,6 +113,22 @@ describe Planting do
end
end
describe '#progress_state' do
let(:planting) { described_class.new }
def states(finished: false, super_late: false, late: false, harvest_time: false)
allow(planting).to receive_messages(finished?: finished, super_late?: super_late, late?: late,
harvest_time?: harvest_time)
planting.progress_state
end
it { expect(states).to eq :growing }
it { expect(states(harvest_time: true)).to eq :harvesting }
it { expect(states(late: true, harvest_time: true)).to eq :late }
it { expect(states(late: true, super_late: true)).to eq :super_late }
it { expect(states(finished: true, late: true)).to eq :finished }
end
describe 'planting first harvest preductions' do
context 'no data' do
let(:planting) { create(:planting) }
@@ -92,6 +92,10 @@ describe GardenCardSerializer do
expect(planting).to include(url: "/plantings/#{annual.slug}", crop: { name: 'lettuce', icon_url: nil })
end
it 'says how the planting is getting on, for the progress bar colour' do
expect(serialize(garden)[:annuals].first[:progress_state]).to eq :growing
end
it 'says why there is no progress bar' do
annual.update!(planted_at: nil)