mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-11-04 20:22:47 -05:00
Enhance crop lifespan and job templates display with new styles and functionality
This commit is contained in:
1 parent
85fe7dde15
commit
ea46d88d3c
8 files changed
+191
-50
No files matched your search
@@ -229,6 +229,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Annual or perennial is the point of the lifespan card, so it is the biggest thing in it.
|
||||
.lifespan-word {
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
// An annual's median lifespan, under the word annual.
|
||||
.crop-median-lifespan {
|
||||
align-items: center;
|
||||
|
||||
@@ -1107,38 +1107,32 @@ $notepad-trail: #a4c98c;
|
||||
}
|
||||
|
||||
.crop-notepad {
|
||||
background: $white;
|
||||
border: 1px solid rgba(65, 63, 59, 0.15);
|
||||
border-radius: $card-radius;
|
||||
box-shadow: 1px 3px 3px 1px #cac1b3;
|
||||
margin-bottom: 1rem;
|
||||
overflow: hidden;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
// Only shown to tell several templates apart, or to say a template is for part of the
|
||||
// year, so it is a subheading, not a second bar inside the section's.
|
||||
.crop-notepad-title {
|
||||
align-items: baseline;
|
||||
background: $green;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 1.1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
// The site makes headings in a section green banners with a shadow; this one
|
||||
// is plain text on the notepad's own bar.
|
||||
// The site makes headings in a section green banners with a shadow; this one is plain.
|
||||
h3 {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
color: $white;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 400;
|
||||
color: $notepad-leaf;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.crop-notepad-season {
|
||||
color: $white;
|
||||
color: $ink-muted;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@@ -1203,13 +1197,13 @@ $notepad-trail: #a4c98c;
|
||||
.crop-job-add {
|
||||
margin: 0.25rem 0 1rem;
|
||||
|
||||
// White on the button, as in the heading bars, not the menu's green.
|
||||
> .menu-icon,
|
||||
.btn > .menu-icon {
|
||||
color: inherit;
|
||||
margin-right: 0.4rem;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// White on the button, as in the heading bars, not the menu's green.
|
||||
.record-page .crop-job-add .menu-icon {
|
||||
color: inherit;
|
||||
margin-right: 0.4rem;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
// The day to plant on, above the button that adds the jobs.
|
||||
|
||||
@@ -189,6 +189,18 @@ class Crop < ApplicationRecord
|
||||
reason_for_rejection
|
||||
end
|
||||
|
||||
# The seed packets of this crop and of its varieties that give a figure for a
|
||||
# packet fact such as row_spacing. A packet that leaves it blank, or says zero,
|
||||
# gives none.
|
||||
def seeds_with(attribute)
|
||||
Seed.of_crop(self).where.not(attribute => [nil, 0])
|
||||
end
|
||||
|
||||
# The median of that figure across those packets, or nil when there are none.
|
||||
def seed_median(attribute)
|
||||
seeds_with(attribute).median(attribute)
|
||||
end
|
||||
|
||||
def update_medians
|
||||
plantings.each(&:update_harvest_days!)
|
||||
update_lifespan_medians
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
.crop-job-templates
|
||||
- templates.each do |template|
|
||||
.crop-notepad
|
||||
.crop-notepad-title
|
||||
%h3= template.name
|
||||
- unless template.season_any?
|
||||
%span.crop-notepad-season= crop_job_option(:season, template.season)
|
||||
-# The section's heading already says what this is: a template's own name is
|
||||
-# only needed to tell several apart, or to say it is for part of the year.
|
||||
- if templates.size > 1 || !template.season_any?
|
||||
.crop-notepad-title
|
||||
%h3= template.name
|
||||
- unless template.season_any?
|
||||
%span.crop-notepad-season= crop_job_option(:season, template.season)
|
||||
%ol.crop-notepad-steps
|
||||
- template.steps.each do |step|
|
||||
%li.crop-job-step
|
||||
|
||||
@@ -11,26 +11,24 @@
|
||||
.card.fact-card.lifespan-card
|
||||
.card-body.text-center
|
||||
%h3
|
||||
= link_to 'https://en.wikipedia.org/wiki/Annual_vs._perennial_plant_evolution', class: 'crop-longevity' do
|
||||
Lifespan
|
||||
= editable :select, crop, :perennial, display_field: '.lifespan-display', values: [['Annual', false], ['Perennial', true]]
|
||||
.lifespan-display
|
||||
= link_to 'https://en.wikipedia.org/wiki/Annual_vs._perennial_plant_evolution', class: 'lifespan-word' do
|
||||
= crop.perennial? ? 'Perennial' : 'Annual'
|
||||
- if crop.perennial?
|
||||
%small living more than two years
|
||||
- else
|
||||
%small living and reproducing in a single year or less
|
||||
- if crop.median_lifespan.present?
|
||||
- weeks = in_weeks(crop.median_lifespan)
|
||||
.crop-median-lifespan
|
||||
%i.far.fa-calendar.fa-2x.amber-text{ 'aria-hidden': 'true' }
|
||||
- if weeks > 1
|
||||
%strong #{weeks} #{'week'.pluralize(weeks)}
|
||||
%small median lifespan
|
||||
- else
|
||||
%small.text-muted We don't have enough data yet on the median lifespan.
|
||||
- if can? :wrangle, @crop
|
||||
%small.edit-link
|
||||
= bootstrap_form_for(@crop) do |f|
|
||||
= f.hidden_field :perennial, value: crop.annual?
|
||||
= f.submit :toggle
|
||||
- if crop.perennial?
|
||||
%small living more than two years
|
||||
- else
|
||||
%small living and reproducing in a single year or less
|
||||
- if crop.median_lifespan.present?
|
||||
- weeks = in_weeks(crop.median_lifespan)
|
||||
.crop-median-lifespan
|
||||
%i.far.fa-calendar.fa-2x.amber-text{ 'aria-hidden': 'true' }
|
||||
- if weeks > 1
|
||||
%strong #{weeks} #{'week'.pluralize(weeks)}
|
||||
%small median lifespan
|
||||
- else
|
||||
%small.text-muted We don't have enough data yet on the median lifespan.
|
||||
|
||||
- if crop.median_days_to_first_harvest.present?
|
||||
= render 'layouts/fact_card',
|
||||
@@ -48,9 +46,14 @@
|
||||
- if crop.spread.present?
|
||||
= render 'layouts/fact_card',
|
||||
title: 'Spread', value: "#{crop.spread}cm", description: nil
|
||||
- if crop.row_spacing.present?
|
||||
-# Members' seed packets say what the crop needs, if the crop itself doesn't: their median.
|
||||
- packets_spacing = crop.seed_median(:row_spacing)
|
||||
- if packets_spacing || crop.row_spacing.present?
|
||||
- spacing = packets_spacing || crop.row_spacing
|
||||
- packets = crop.seeds_with(:row_spacing).count if packets_spacing
|
||||
= render 'layouts/fact_card',
|
||||
title: 'Row Spacing', value: "#{crop.row_spacing}cm", description: nil
|
||||
title: 'Row Spacing', value: "#{number_with_precision(spacing, precision: 1, strip_insignificant_zeros: true)}cm",
|
||||
description: (packets ? "median of #{pluralize(packets, 'seed packet')}" : nil)
|
||||
- if crop.growing_degree_days.present?
|
||||
= render 'layouts/fact_card',
|
||||
title: 'Growing Degree Days', value: crop.growing_degree_days, description: nil
|
||||
|
||||
@@ -707,4 +707,56 @@ describe Crop do
|
||||
expect(described_class.icon_names).to eq described_class.icon_names.sort
|
||||
end
|
||||
end
|
||||
|
||||
describe 'facts from seed packets' do
|
||||
subject(:crop) { create(:tomato) }
|
||||
|
||||
let(:variety) { create(:crop, name: 'cherry tomato', parent: crop) }
|
||||
|
||||
describe '#seed_median' do
|
||||
it { expect(crop.seed_median(:row_spacing)).to be_nil }
|
||||
|
||||
context 'with packets that give a row spacing' do
|
||||
before do
|
||||
create(:seed, crop:, row_spacing: 30)
|
||||
create(:seed, crop:, row_spacing: 60)
|
||||
create(:seed, crop:, row_spacing: 90)
|
||||
end
|
||||
|
||||
it { expect(crop.seed_median(:row_spacing)).to eq 60 }
|
||||
end
|
||||
|
||||
context 'with an even number of packets' do
|
||||
before do
|
||||
create(:seed, crop:, row_spacing: 30)
|
||||
create(:seed, crop:, row_spacing: 50)
|
||||
end
|
||||
|
||||
it { expect(crop.seed_median(:row_spacing)).to eq 40 }
|
||||
end
|
||||
|
||||
context 'with a packet that leaves it blank, or says zero' do
|
||||
before do
|
||||
create(:seed, crop:, row_spacing: 30)
|
||||
create(:seed, crop:, row_spacing: nil)
|
||||
create(:seed, crop:).update_column(:row_spacing, 0) # rubocop:disable Rails/SkipsModelValidations
|
||||
end
|
||||
|
||||
it { expect(crop.seed_median(:row_spacing)).to eq 30 }
|
||||
it { expect(crop.seeds_with(:row_spacing).count).to eq 1 }
|
||||
end
|
||||
|
||||
context 'with a packet of a variety' do
|
||||
before { create(:seed, crop: variety, row_spacing: 45) }
|
||||
|
||||
it { expect(crop.seed_median(:row_spacing)).to eq 45 }
|
||||
end
|
||||
|
||||
context 'with a packet of another crop' do
|
||||
before { create(:seed, crop: create(:crop, name: 'carrot'), row_spacing: 10) }
|
||||
|
||||
it { expect(crop.seed_median(:row_spacing)).to be_nil }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@ describe CropsController, '#show job templates' do
|
||||
|
||||
before { get crop_path(crop) }
|
||||
|
||||
it { is_expected.to have_css('.crop-job-templates h3', text: 'Tomato jobs') }
|
||||
it { is_expected.to have_no_css('.crop-job-templates h3') }
|
||||
it { is_expected.to have_css('.crop-job-step', text: 'Stake or cage the plants') }
|
||||
it { is_expected.to have_css('.crop-job-step', text: '2 weeks after planting out') }
|
||||
|
||||
@@ -95,6 +95,23 @@ describe CropsController, '#show job templates' do
|
||||
it { is_expected.to have_no_css('.crop-job-add') }
|
||||
end
|
||||
|
||||
context 'with two templates' do
|
||||
before do
|
||||
create(:crop_job_step, crop_job_template: create(:crop_job_template, crop:, name: 'Early jobs', status: 'published'))
|
||||
get crop_path(crop)
|
||||
end
|
||||
|
||||
it { is_expected.to have_css('.crop-job-templates h3', text: 'Early jobs') }
|
||||
it { is_expected.to have_css('.crop-job-templates h3', text: 'Tomato jobs') }
|
||||
end
|
||||
|
||||
context 'with a template for part of the year' do
|
||||
let(:template) { create(:crop_job_template, crop:, name: 'Tomato jobs', status: 'published', season: 'spring_summer') }
|
||||
|
||||
it { is_expected.to have_css('.crop-job-templates h3', text: 'Tomato jobs') }
|
||||
it { is_expected.to have_css('.crop-notepad-season', text: 'Spring and summer') }
|
||||
end
|
||||
|
||||
context 'with a draft template' do
|
||||
let(:template) { create(:crop_job_template, crop:, name: 'Tomato jobs', status: 'draft') }
|
||||
|
||||
@@ -106,7 +123,7 @@ describe CropsController, '#show job templates' do
|
||||
|
||||
before { get crop_path(variety) }
|
||||
|
||||
it { is_expected.to have_css('.crop-job-templates h3', text: 'Tomato jobs') }
|
||||
it { is_expected.to have_no_css('.crop-job-templates h3') }
|
||||
end
|
||||
|
||||
context 'with a template for another crop' do
|
||||
|
||||
@@ -89,7 +89,7 @@ describe CropsController, '#show sections' do
|
||||
context 'with an annual with a median lifespan of 13 weeks' do
|
||||
let(:perennial) { false }
|
||||
|
||||
it { is_expected.to have_css('.lifespan-card h3', text: 'Annual') }
|
||||
it { is_expected.to have_css('.lifespan-card .lifespan-word', text: 'Annual') }
|
||||
it { is_expected.to have_css('.lifespan-card .crop-median-lifespan', text: '13 weeks median lifespan') }
|
||||
it { is_expected.to have_css('.lifespan-card .fa-calendar') }
|
||||
it { is_expected.to have_no_css('.lifespan-card svg') }
|
||||
@@ -113,7 +113,7 @@ describe CropsController, '#show sections' do
|
||||
context 'with a perennial' do
|
||||
let(:perennial) { true }
|
||||
|
||||
it { is_expected.to have_css('.lifespan-card h3', text: 'Perennial') }
|
||||
it { is_expected.to have_css('.lifespan-card .lifespan-word', text: 'Perennial') }
|
||||
it { is_expected.to have_no_css('.crop-median-lifespan') }
|
||||
end
|
||||
end
|
||||
@@ -134,4 +134,55 @@ describe CropsController, '#show sections' do
|
||||
|
||||
it { is_expected.to have_no_css('section.crop-jobs') }
|
||||
end
|
||||
|
||||
describe 'changing whether the crop is annual or perennial' do
|
||||
context 'when signed in as a crop wrangler' do
|
||||
before do
|
||||
sign_in create(:crop_wrangling_member)
|
||||
get crop_path(crop)
|
||||
end
|
||||
|
||||
it { is_expected.to have_css('.lifespan-card .edit-link a.editable') }
|
||||
it { is_expected.to have_css('.lifespan-card select[name="crop[perennial]"]', visible: :all) }
|
||||
it { is_expected.to have_no_css('.lifespan-card input[type="submit"][value="toggle"]', visible: :all) }
|
||||
end
|
||||
|
||||
context 'when signed in as an ordinary member' do
|
||||
before do
|
||||
sign_in create(:member)
|
||||
get crop_path(crop)
|
||||
end
|
||||
|
||||
it { is_expected.to have_no_css('.lifespan-card .edit-link') }
|
||||
it { is_expected.to have_no_css('.lifespan-card select', visible: :all) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'the row spacing card' do
|
||||
context 'with seed packets that give a row spacing' do
|
||||
before do
|
||||
create(:seed, crop:, row_spacing: 30)
|
||||
create(:seed, crop:, row_spacing: 60)
|
||||
create(:seed, crop:, row_spacing: nil)
|
||||
get crop_path(crop)
|
||||
end
|
||||
|
||||
it { is_expected.to have_css('.fact-card', text: 'Row Spacing 45cm median of 2 seed packets') }
|
||||
end
|
||||
|
||||
context 'with a crop row spacing and no packets that give one' do
|
||||
let(:crop) { create(:tomato, row_spacing: 25) }
|
||||
|
||||
before { get crop_path(crop) }
|
||||
|
||||
it { is_expected.to have_css('.fact-card', text: 'Row Spacing 25cm') }
|
||||
it { is_expected.to have_no_css('.fact-card', text: 'median of') }
|
||||
end
|
||||
|
||||
context 'with neither' do
|
||||
before { get crop_path(crop) }
|
||||
|
||||
it { is_expected.to have_no_css('.fact-card', text: 'Row Spacing') }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user