summaryrefslogtreecommitdiff
path: root/app/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/concerns/.keep0
-rw-r--r--app/controllers/crops_controller.rb70
-rw-r--r--app/controllers/dashboard_controller.rb45
-rw-r--r--app/controllers/fertilizer_products_controller.rb70
-rw-r--r--app/controllers/fertilizers_controller.rb70
-rw-r--r--app/controllers/nutrient_measurement_controller.rb4
-rw-r--r--app/controllers/rafts_controller.rb54
-rw-r--r--app/controllers/recipes_controller.rb8
9 files changed, 325 insertions, 0 deletions
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
new file mode 100644
index 0000000..0d95db2
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,4 @@
+class ApplicationController < ActionController::Base
+ # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
+ allow_browser versions: :modern
+end
diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/controllers/concerns/.keep
diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb
new file mode 100644
index 0000000..9619852
--- /dev/null
+++ b/app/controllers/crops_controller.rb
@@ -0,0 +1,70 @@
+class CropsController < ApplicationController
+ before_action :set_crop, only: %i[ show edit update destroy ]
+
+ # GET /crops or /crops.json
+ def index
+ @crops = Crop.all
+ end
+
+ # GET /crops/1 or /crops/1.json
+ def show
+ end
+
+ # GET /crops/new
+ def new
+ @crop = Crop.new
+ end
+
+ # GET /crops/1/edit
+ def edit
+ end
+
+ # POST /crops or /crops.json
+ def create
+ @crop = Crop.new(crop_params)
+
+ respond_to do |format|
+ if @crop.save
+ format.html { redirect_to @crop, notice: "Crop was successfully created." }
+ format.json { render :show, status: :created, location: @crop }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @crop.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /crops/1 or /crops/1.json
+ def update
+ respond_to do |format|
+ if @crop.update(crop_params)
+ format.html { redirect_to @crop, notice: "Crop was successfully updated.", status: :see_other }
+ format.json { render :show, status: :ok, location: @crop }
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @crop.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /crops/1 or /crops/1.json
+ def destroy
+ @crop.destroy!
+
+ respond_to do |format|
+ format.html { redirect_to crops_path, notice: "Crop was successfully destroyed.", status: :see_other }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_crop
+ @crop = Crop.find(params.expect(:id))
+ end
+
+ # Only allow a list of trusted parameters through.
+ def crop_params
+ params.expect(crop: [ :name, :crop_type, :nno3, :p, :k, :ca, :mg, :s, :na, :cl, :si, :fe, :zn, :b, :mn, :cu, :mo, :nnh4 ])
+ end
+end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
new file mode 100644
index 0000000..4e9d560
--- /dev/null
+++ b/app/controllers/dashboard_controller.rb
@@ -0,0 +1,45 @@
+class DashboardController < ApplicationController
+ def index
+ # Raft allocation by crop type
+ @raft_data = raft_data_series
+
+ # Nutrient target table
+ @latest_measurement = NutrientMeasurement.order(measured_on: :desc, created_at: :desc).first
+ @target = TargetNutrientCalculator.call
+
+ # Measurement history table
+ @measurements = NutrientMeasurement.order(measured_on: :desc).limit(10)
+
+ @npk_measurement_data = measurement_data_series(:nno3, :p, :k)
+ @ammonium_measurement_data = measurement_data_series(:nnh4)
+ end
+
+ private
+
+ def raft_data_series
+ data_series = []
+
+ counts = Raft.left_outer_joins(:crop)
+ .group("crops.name", "crops.crop_type")
+ .count
+
+ counts.each do |(crop_name, crop_type), count|
+ name = (crop_name || "unassigned").titleize
+ type = (crop_type || "unassigned").titleize
+ data = { type => count }
+ data_series << { name:, data: }
+ end
+
+ unassigned, assigned = data_series.partition { |s| s[:name].casecmp("unassigned").zero? }
+ assigned + unassigned
+ end
+
+ def measurement_data_series(*nutrients)
+ nutrients.map do |formula|
+ { name: Nutrient.find_by!(formula:).name,
+ data: NutrientMeasurement
+ .order(:measured_on)
+ .pluck(:measured_on, formula) }
+ end
+ end
+end
diff --git a/app/controllers/fertilizer_products_controller.rb b/app/controllers/fertilizer_products_controller.rb
new file mode 100644
index 0000000..ff0d945
--- /dev/null
+++ b/app/controllers/fertilizer_products_controller.rb
@@ -0,0 +1,70 @@
+class FertilizerProductsController < ApplicationController
+ before_action :set_fertilizer_product, only: %i[ show edit update destroy ]
+
+ # GET /fertilizer_products or /fertilizer_products.json
+ def index
+ @fertilizer_products = FertilizerProduct.all
+ end
+
+ # GET /fertilizer_products/1 or /fertilizer_products/1.json
+ def show
+ end
+
+ # GET /fertilizer_products/new
+ def new
+ @fertilizer_product = FertilizerProduct.new
+ end
+
+ # GET /fertilizer_products/1/edit
+ def edit
+ end
+
+ # POST /fertilizer_products or /fertilizer_products.json
+ def create
+ @fertilizer_product = FertilizerProduct.new(fertilizer_product_params)
+
+ respond_to do |format|
+ if @fertilizer_product.save
+ format.html { redirect_to @fertilizer_product, notice: "Fertilizer product was successfully created." }
+ format.json { render :show, status: :created, location: @fertilizer_product }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @fertilizer_product.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /fertilizer_products/1 or /fertilizer_products/1.json
+ def update
+ respond_to do |format|
+ if @fertilizer_product.update(fertilizer_product_params)
+ format.html { redirect_to @fertilizer_product, notice: "Fertilizer product was successfully updated.", status: :see_other }
+ format.json { render :show, status: :ok, location: @fertilizer_product }
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @fertilizer_product.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /fertilizer_products/1 or /fertilizer_products/1.json
+ def destroy
+ @fertilizer_product.destroy!
+
+ respond_to do |format|
+ format.html { redirect_to fertilizer_products_path, notice: "Fertilizer product was successfully destroyed.", status: :see_other }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_fertilizer_product
+ @fertilizer_product = FertilizerProduct.find(params.expect(:id))
+ end
+
+ # Only allow a list of trusted parameters through.
+ def fertilizer_product_params
+ params.expect(fertilizer_product: [ :name, :purity ])
+ end
+end
diff --git a/app/controllers/fertilizers_controller.rb b/app/controllers/fertilizers_controller.rb
new file mode 100644
index 0000000..040c462
--- /dev/null
+++ b/app/controllers/fertilizers_controller.rb
@@ -0,0 +1,70 @@
+class FertilizersController < ApplicationController
+ before_action :set_fertilizer, only: %i[ show edit update destroy ]
+
+ # GET /fertilizers or /fertilizers.json
+ def index
+ @fertilizers = Fertilizer.all
+ end
+
+ # GET /fertilizers/1 or /fertilizers/1.json
+ def show
+ end
+
+ # GET /fertilizers/new
+ def new
+ @fertilizer = Fertilizer.new
+ end
+
+ # GET /fertilizers/1/edit
+ def edit
+ end
+
+ # POST /fertilizers or /fertilizers.json
+ def create
+ @fertilizer = Fertilizer.new(fertilizer_params)
+
+ respond_to do |format|
+ if @fertilizer.save
+ format.html { redirect_to @fertilizer, notice: "Fertilizer was successfully created." }
+ format.json { render :show, status: :created, location: @fertilizer }
+ else
+ format.html { render :new, status: :unprocessable_entity }
+ format.json { render json: @fertilizer.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PATCH/PUT /fertilizers/1 or /fertilizers/1.json
+ def update
+ respond_to do |format|
+ if @fertilizer.update(fertilizer_params)
+ format.html { redirect_to @fertilizer, notice: "Fertilizer was successfully updated.", status: :see_other }
+ format.json { render :show, status: :ok, location: @fertilizer }
+ else
+ format.html { render :edit, status: :unprocessable_entity }
+ format.json { render json: @fertilizer.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /fertilizers/1 or /fertilizers/1.json
+ def destroy
+ @fertilizer.destroy!
+
+ respond_to do |format|
+ format.html { redirect_to fertilizers_path, notice: "Fertilizer was successfully destroyed.", status: :see_other }
+ format.json { head :no_content }
+ end
+ end
+
+ private
+ # Use callbacks to share common setup or constraints between actions.
+ def set_fertilizer
+ @fertilizer = Fertilizer.find(params.expect(:id))
+ end
+
+ # Only allow a list of trusted parameters through.
+ def fertilizer_params
+ params.expect(fertilizer: [ :name, :formula, :nno3, :nnh4, :p, :k, :ca, :mg, :s, :na, :cl, :si, :fe, :zn, :b, :mn, :cu, :mo ])
+ end
+end
diff --git a/app/controllers/nutrient_measurement_controller.rb b/app/controllers/nutrient_measurement_controller.rb
new file mode 100644
index 0000000..6d26bfa
--- /dev/null
+++ b/app/controllers/nutrient_measurement_controller.rb
@@ -0,0 +1,4 @@
+class NutrientMeasurementController < ApplicationController
+ def index
+ end
+end
diff --git a/app/controllers/rafts_controller.rb b/app/controllers/rafts_controller.rb
new file mode 100644
index 0000000..96d99fd
--- /dev/null
+++ b/app/controllers/rafts_controller.rb
@@ -0,0 +1,54 @@
+class RaftsController < ApplicationController
+ before_action :set_collections, only: [ :editor ]
+
+ def index
+ redirect_to editor_rafts_path
+ end
+
+ def editor
+ # @beds, @crops set in before_action
+ end
+
+ # 1) Assign ALL rafts
+ def assign_all
+ crop_id = normalized_crop_id(params[:crop_id])
+ Raft.update_all(crop_id: crop_id) # nil ok
+ redirect_to editor_rafts_path, notice: "All rafts updated."
+ end
+
+ # 2) Assign all rafts in ONE bed
+ def assign_bed
+ bed = Bed.find(params.require(:bed_id))
+ crop_id = normalized_crop_id(params[:crop_id])
+ bed.rafts.update_all(crop_id: crop_id)
+ redirect_to editor_rafts_path, notice: "Bed ##{bed.location} updated."
+ end
+
+ # 3) Assign ONE raft
+ def assign_one
+ raft = Raft.find(params[:id])
+ val = params[:crop_id].to_s
+
+ if val.blank? || val.casecmp("null").zero?
+ # Skip validations; write NULL directly
+ raft.update_column(:crop_id, nil)
+ else
+ raft.update!(crop_id: val)
+ end
+
+ redirect_to editor_rafts_path(anchor: "bed-#{raft.bed_id}"), notice: "Raft updated."
+ end
+
+ private
+
+ def set_collections
+ @beds = Bed.order(:location)
+ @crops = Crop.order(:name)
+ end
+
+ # Accept "", "null" → nil to allow clearing
+ def normalized_crop_id(val)
+ return nil if val.blank? || val.to_s.downcase == "null"
+ val
+ end
+end
diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb
new file mode 100644
index 0000000..bcc29e7
--- /dev/null
+++ b/app/controllers/recipes_controller.rb
@@ -0,0 +1,8 @@
+class RecipesController < ApplicationController
+ def show
+ @latest = NutrientMeasurement.order(:measured_on).last || NutrientMeasurement.new
+ @target = TargetNutrientCalculator.call
+ volume = (params[:volume].presence || 100_000).to_i
+ @recipe = FertilizerRecipeCalculator.call(@latest, @target, water_volume_l: volume)
+ end
+end
Copyright 2019--2025 Marius PETER