diff options
Diffstat (limited to 'app/controllers')
-rw-r--r-- | app/controllers/application_controller.rb | 4 | ||||
-rw-r--r-- | app/controllers/concerns/.keep | 0 | ||||
-rw-r--r-- | app/controllers/crops_controller.rb | 70 | ||||
-rw-r--r-- | app/controllers/dashboard_controller.rb | 45 | ||||
-rw-r--r-- | app/controllers/fertilizer_products_controller.rb | 70 | ||||
-rw-r--r-- | app/controllers/fertilizers_controller.rb | 70 | ||||
-rw-r--r-- | app/controllers/nutrient_measurement_controller.rb | 4 | ||||
-rw-r--r-- | app/controllers/rafts_controller.rb | 54 | ||||
-rw-r--r-- | app/controllers/recipes_controller.rb | 8 |
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 |