From 2ff9ba4d67716749c40cc5794a20ee7406ee2651 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:11:03 +0200 Subject: [PATCH 01/51] feat: add value/label for p_app_method --- fdm-core/src/db/schema.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/fdm-core/src/db/schema.ts b/fdm-core/src/db/schema.ts index 69b45a230..8935916a9 100644 --- a/fdm-core/src/db/schema.ts +++ b/fdm-core/src/db/schema.ts @@ -142,15 +142,19 @@ export type fertilizerAcquiringTypeInsert = typeof fertilizerAcquiring.$inferInsert // Define fertilizers application table -export const applicationMethodEnum = fdmSchema.enum("p_app_method", [ - "slotted coulter", - "incorporation", - "injection", - "spraying", - "broadcasting", - "spoke wheel", - "pocket placement", -]) +export const applicationMethodOptions = [ + { value: "slotted coulter", label: "Zodenbemester / Sleepvoet" }, + { value: "incorporation", label: "Inwerken" }, + { value: "injection", label: "Injecteren" }, + { value: "spraying", label: "Spuiten" }, + { value: "broadcasting", label: "Breedwerpig uitstrooien" }, + { value: "spoke wheel", label: "Spaakwiel" }, + { value: "pocket placement", label: "Plantgat" }, +] +export const applicationMethodEnum = fdmSchema.enum( + "p_app_method", + applicationMethodOptions.map((x) => x.value) as [string, ...string[]], +) export const fertilizerApplication = fdmSchema.table( "fertilizer_applying", { From a58b36702989580037c9d62d86e3dd6904e7d6d7 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:15:45 +0200 Subject: [PATCH 02/51] feat: Add new function `getSoilParameterDescription` to obtain details about fertilizer parameters --- .changeset/shaggy-dancers-work.md | 5 + fdm-core/src/fertilizer.d.ts | 66 ++++++++ fdm-core/src/fertilizer.test.ts | 44 +++++ fdm-core/src/fertilizer.ts | 258 +++++++++++++++++++++++++++++- fdm-core/src/index.ts | 1 + 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 .changeset/shaggy-dancers-work.md diff --git a/.changeset/shaggy-dancers-work.md b/.changeset/shaggy-dancers-work.md new file mode 100644 index 000000000..6ee715edf --- /dev/null +++ b/.changeset/shaggy-dancers-work.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-core": minor +--- + +Add new function `getSoilParameterDescription` to obtain details about fertilizer parameters diff --git a/fdm-core/src/fertilizer.d.ts b/fdm-core/src/fertilizer.d.ts index 7c6883c19..a5d43708c 100644 --- a/fdm-core/src/fertilizer.d.ts +++ b/fdm-core/src/fertilizer.d.ts @@ -45,3 +45,69 @@ export interface FertilizerApplication { p_app_date: Date | null p_app_id: string } + +export type FertilizerParameters = + | "p_id_catalogue" + | "p_source" + | "p_name_nl" + | "p_name_en" + | "p_description" + | "p_app_method_options" + | "p_dm" + | "p_density" + | "p_om" + | "p_a" + | "p_hc" + | "p_eom" + | "p_eoc" + | "p_c_rt" + | "p_c_of" + | "p_c_if" + | "p_c_fr" + | "p_cn_of" + | "p_n_rt" + | "p_n_if" + | "p_n_of" + | "p_n_wc" + | "p_p_rt" + | "p_k_rt" + | "p_mg_rt" + | "p_ca_rt" + | "p_ne" + | "p_s_rt" + | "p_s_wc" + | "p_cu_rt" + | "p_zn_rt" + | "p_na_rt" + | "p_si_rt" + | "p_b_rt" + | "p_mn_rt" + | "p_ni_rt" + | "p_fe_rt" + | "p_mo_rt" + | "p_co_rt" + | "p_as_rt" + | "p_cd_rt" + | "p_cr_rt" + | "p_cr_vi" + | "p_pb_rt" + | "p_hg_rt" + | "p_cl_rt" + | "p_type_manure" + | "p_type_mineral" + | "p_type_compost" + +export interface FertilizerParameterDescriptionItem { + parameter: FertilizerParameters + unit: string + type: "numeric" | "enum" | "date" | "text" + name: string + description: string + category: "general" | "primary" | "secondary" | "trace" | "heavy_metals" | "physical" + min?: number + max?: number + options?: schema.applicationMethodOptions +} + +export type FertilizerParameterDescription = + FertilizerParameterDescriptionItem[] diff --git a/fdm-core/src/fertilizer.test.ts b/fdm-core/src/fertilizer.test.ts index 3c4fc6bb7..cc28d74e3 100644 --- a/fdm-core/src/fertilizer.test.ts +++ b/fdm-core/src/fertilizer.test.ts @@ -21,6 +21,7 @@ import { getFertilizer, getFertilizerApplication, getFertilizerApplications, + getFertilizerParametersDescription, getFertilizers, getFertilizersFromCatalogue, removeFertilizer, @@ -1038,3 +1039,46 @@ describe("Fertilizer Data Model", () => { }) }) }) + +describe("getFertilizerParametersDescription", () => { + it("should return the correct fertilizer parameter descriptions for NL-nl locale", () => { + const descriptions = getFertilizerParametersDescription("NL-nl") + expect(descriptions).toHaveLength(42) + for (const description of descriptions) { + expect(description).toHaveProperty("parameter") + expect(description).toHaveProperty("unit") + expect(description).toHaveProperty("name") + expect(description).toHaveProperty("type") + expect(description).toHaveProperty("description") + expect(description).toHaveProperty("category") + if (description.type === "enum") { + expect(description).toHaveProperty("options") + } + } + }) + + it("should throw an error for unsupported locales", () => { + expect(() => getFertilizerParametersDescription("en-US")).toThrowError( + "Unsupported locale", + ) + expect(() => getFertilizerParametersDescription("de-DE")).toThrowError( + "Unsupported locale", + ) + }) + + it("should return the correct fertilizer parameter descriptions for default locale", () => { + const descriptions = getFertilizerParametersDescription() + expect(descriptions).toHaveLength(42) + for (const description of descriptions) { + expect(description).toHaveProperty("parameter") + expect(description).toHaveProperty("unit") + expect(description).toHaveProperty("name") + expect(description).toHaveProperty("type") + expect(description).toHaveProperty("description") + expect(description).toHaveProperty("category") + if (description.type === "enum") { + expect(description).toHaveProperty("options") + } + } + }) +}) diff --git a/fdm-core/src/fertilizer.ts b/fdm-core/src/fertilizer.ts index 45b8fa4db..6ebe77c25 100644 --- a/fdm-core/src/fertilizer.ts +++ b/fdm-core/src/fertilizer.ts @@ -7,7 +7,12 @@ import type { PrincipalId } from "./authorization.d" import * as schema from "./db/schema" import { handleError } from "./error" import type { FdmType } from "./fdm" -import type { Fertilizer, FertilizerApplication } from "./fertilizer.d" +import type { + FertilizefrParameterDescription, + Fertilizer, + FertilizerApplication, + FertilizerParameterDescription, +} from "./fertilizer.d" import type { Timeframe } from "./timeframe" /** @@ -907,3 +912,254 @@ export async function getFertilizerApplications( }) } } + +/** + * Retrieves a description of the available fertilizer parameters. + * + * This function returns an array of objects, each describing a fertilizer parameter. + * Each description includes the parameter's name, unit, type (numeric or enum), + * a human-readable name, a detailed description, and optional constraints like + * minimum and maximum values or a list of valid options for enum types. + * + * @param locale - The locale for which to retrieve the descriptions. Currently only 'NL-nl' is supported. + * @returns An array of fertilizerParameterDescriptionItem objects. + * @throws {Error} If an unsupported locale is provided. + */ +export function getFertilizerParametersDescription( + locale = "NL-nl", +): FertilizerParameterDescription { + if (locale !== "NL-nl") throw new Error("Unsupported locale") + const fertilizerParameterDescription: FertilizerParameterDescription = [ + { + parameter: "p_id_catalogue", + unit: "", + name: "ID", + type: "text", + category: "general", + description: "Catalogu ID van meststof", + }, + { + parameter: "p_source", + unit: "", + name: "Bron", + type: "text", + category: "general", + description: "Gegevensbron van meststof", + }, + { + parameter: "p_name_nl", + unit: "", + name: "Naam", + type: "text", + category: "general", + description: "Nederlandse naam van meststof", + }, + // { + // parameter: "p_name_en", + // unit: "", + // name: "Naam (Engels)", + // type: "text", + // category: "general", + // description: "Engelse naam van meststof", + // }, + // { + // parameter: "p_description", + // unit: "", + // name: "Beschrijving", + // type: "text", + // category: "general", + // description: "Beschrijvingen en/of opmerkingen over de meststof", + // }, + { + parameter: "p_app_method_options", + unit: "", + name: "Toedieningsmethodes", + type: "enum", + category: "general", + description: "Toedieningsmethodes beschikbaar voor deze methode", + options: schema.applicationMethodOptions, + }, + { + parameter: "p_dm", + unit: "g/kg", + name: "Droge stofgehalte", + type: "numeric", + category: "physical", + description: "", + min: 0, + max: 1000, + }, + { + parameter: "p_density", + unit: "kg/l", + name: "Dichtheid", + type: "numeric", + category: "physical", + description: "", + min: 0.00016, + max: 17.31, + }, + { + parameter: "p_n_rt", + unit: "g N/kg", + name: "N", + type: "numeric", + category: "primary", + description: "Stikstof, totaal", + min: 0, + max: 1000, + }, + { + parameter: "p_n_wc", + unit: "-", + name: "N-werking", + type: "numeric", + category: "primary", + description: "Stikstof, werkingscoëfficient", + min: 0, + max: 1, + }, + { + parameter: "p_p_rt", + unit: "g P2O5/kg", + name: "P", + type: "numeric", + category: "primary", + description: "Fosfaat", + min: 0, + max: 4583, + }, + { + parameter: "p_k_rt", + unit: "g K2O/kg", + name: "K", + type: "numeric", + category: "primary", + description: "Kalium", + min: 0, + max: 2409.2, + }, + { + parameter: "p_eoc", + unit: "g EOC/kg", + name: "EOC", + type: "numeric", + category: "secondary", + description: "Koolstof, effectief", + min: 0, + max: 1000, + }, + { + parameter: "p_s_rt", + unit: "g SO3/kg", + name: "S", + type: "numeric", + category: "secondary", + description: "Zwavel", + min: 0, + max: 2497.2, + }, + { + parameter: "p_mg_rt", + unit: "g MgO/kg", + name: "Mg", + type: "numeric", + category: "secondary", + description: "Magnesium", + min: 0, + max: 1659, + }, + { + parameter: "p_ca_rt", + unit: "g CaO/kg", + name: "Ca", + type: "numeric", + category: "secondary", + description: "Calcium", + min: 0, + max: 1399.2, + }, + { + parameter: "p_na_rt", + unit: "g Na2O/kg", + name: "Na", + type: "numeric", + category: "secondary", + description: "Calcium", + min: 0, + max: 2695900, + }, + { + parameter: "p_cu_rt", + unit: "mg Cu/kg", + name: "Cu", + type: "numeric", + category: "trace", + description: "Koper", + min: 0, + max: 1000000, + }, + { + parameter: "p_zn_rt", + unit: "mg Zn/kg", + name: "Zn", + type: "numeric", + category: "trace", + description: "Koper", + min: 0, + max: 1000000, + }, + { + parameter: "p_co_rt", + unit: "mg Co/kg", + name: "Co", + type: "numeric", + category: "trace", + description: "Koper", + min: 0, + max: 1000000, + }, + { + parameter: "p_co_rt", + unit: "mg Co/kg", + name: "Co", + type: "numeric", + category: "trace", + description: "Koper", + min: 0, + max: 1000000, + }, + { + parameter: "p_mn_rt", + unit: "mg Mn/kg", + name: "Mn", + type: "numeric", + category: "trace", + description: "Mangaan", + min: 0, + max: 1000000, + }, + { + parameter: "p_mo_rt", + unit: "mg Mn/kg", + name: "Mo", + type: "numeric", + category: "trace", + description: "Molybdeen", + min: 0, + max: 1000000, + }, + { + parameter: "p_b_rt", + unit: "mg B/kg", + name: "B", + type: "numeric", + category: "trace", + description: "boor", + min: 0, + max: 1000000, + }, + ] + + return fertilizerParameterDescription +} diff --git a/fdm-core/src/index.ts b/fdm-core/src/index.ts index 89732a10f..1a14eece5 100644 --- a/fdm-core/src/index.ts +++ b/fdm-core/src/index.ts @@ -52,6 +52,7 @@ export { removeFertilizerApplication, getFertilizerApplication, getFertilizerApplications, + getFertilizerParametersDescription, } from "./fertilizer" export type { Fertilizer, From 2fb2db33f904606a849b4b6689f9b2a161803811 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:52:10 +0200 Subject: [PATCH 03/51] feat: Improve design of the fertilizer form page by making it more intuitive and clear --- .changeset/quick-states-melt.md | 5 + .../components/blocks/fertilizer/form-old.tsx | 482 ++++++++++++ .../app/components/blocks/fertilizer/form.tsx | 695 +++++++----------- .../blocks/fertilizer/formschema.tsx | 154 +++- fdm-app/app/components/ui/radio-group.tsx | 43 ++ .../farm.$b_id_farm.fertilizers.$p_id.tsx | 2 +- .../farm.$b_id_farm.fertilizers.new.tsx | 265 ++++--- fdm-app/package.json | 1 + pnpm-lock.yaml | 34 + 9 files changed, 1126 insertions(+), 555 deletions(-) create mode 100644 .changeset/quick-states-melt.md create mode 100644 fdm-app/app/components/blocks/fertilizer/form-old.tsx create mode 100644 fdm-app/app/components/ui/radio-group.tsx diff --git a/.changeset/quick-states-melt.md b/.changeset/quick-states-melt.md new file mode 100644 index 000000000..0757398c7 --- /dev/null +++ b/.changeset/quick-states-melt.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Improve design of the fertilizer form page by making it more intuitive and clear diff --git a/fdm-app/app/components/blocks/fertilizer/form-old.tsx b/fdm-app/app/components/blocks/fertilizer/form-old.tsx new file mode 100644 index 000000000..ccf9f0bab --- /dev/null +++ b/fdm-app/app/components/blocks/fertilizer/form-old.tsx @@ -0,0 +1,482 @@ +import type { UseFormReturn } from "react-hook-form" +import { Form } from "react-router" +import { RemixFormProvider } from "remix-hook-form" +import type { ZodType, z } from "zod" +import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { Badge } from "~/components/ui/badge" +import { Button } from "~/components/ui/button" +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "~/components/ui/card" +import { + FormControl, + FormDescription, + FormField, + FormItem, + FormMessage, +} from "~/components/ui/form" +import { Input } from "~/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/ui/select" +import type { Fertilizer } from "./columns" + +export function FertilizerForm({ + fertilizer, + form, + editable, + farm, +}: { + fertilizer: Fertilizer + form: UseFormReturn< + z.infer, + ZodType, + undefined + > + editable: boolean + farm: { + b_id_farm: string + b_name_farm: string + } +}) { + return ( + +
+
+
+ + + Algemene informatie + + Details over de meststof + + + +
+ Naam +
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_name_nl} + )} +
+
+
+ + Catalogus + + + {fertilizer.p_source === + farm.b_id_farm ? ( + + {farm.b_name_farm} + + ) : ( + + {fertilizer.p_source} + + )} + +
+
+ Type + {editable ? ( + ( + + + + + + )} + /> + ) : ( + + {fertilizer.p_type_manure ? ( + + Mest + + ) : null} + {fertilizer.p_type_compost ? ( + + Compost + + ) : null} + {fertilizer.p_type_mineral ? ( + + Kunstmest + + ) : null} + + )} +
+
+ {editable && ( + + + + )} +
+ + + Samenstelling + + De gehalten van deze meststof + + + +
+ {/* Stikstof Row */} +
Stikstof
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_n_rt} + )} +
+
+ g N / kg +
+ + {/* Stikstof, werkingscoëfficiënt Row */} +
+ Stikstof, werkingscoëfficiënt +
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_n_wc} + )} +
+
+ - +
+ {/* Fosfaat Row */} +
Fosfaat
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_p_rt} + )} +
+
+ g P2O5 / kg +
+ + {/* Kalium Row */} +
Kalium
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_k_rt} + )} +
+
+ g K2O / kg +
+ + {/* Organische stof Row */} +
+ Organische stof +
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_om} + )} +
+
+ g OS / kg +
+ + {/* Koolstof, effectief Row */} +
+ Koolstof, effectief +
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_eoc} + )} +
+
+ g EOC / kg +
+ + {/* Zwavel Row */} +
Zwavel
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_s_rt} + )} +
+
+ g SO3 / kg +
+ + {/* Calcium (Ca) Row */} +
Calcium
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_ca_rt} + )} +
+
+ g CaO / kg +
+ + {/* Magnesium (Mg) Row */} +
Magnesium
+
+ {editable ? ( + ( + + + + + + + + )} + /> + ) : ( + {fertilizer.p_mg_rt} + )} +
+
+ g MgO / kg +
+
+
+ {editable && ( + + + + )} +
+
+
+
+
+ ) +} diff --git a/fdm-app/app/components/blocks/fertilizer/form.tsx b/fdm-app/app/components/blocks/fertilizer/form.tsx index ccf9f0bab..6106fc990 100644 --- a/fdm-app/app/components/blocks/fertilizer/form.tsx +++ b/fdm-app/app/components/blocks/fertilizer/form.tsx @@ -1,53 +1,155 @@ -import type { UseFormReturn } from "react-hook-form" -import { Form } from "react-router" -import { RemixFormProvider } from "remix-hook-form" -import type { ZodType, z } from "zod" -import { LoadingSpinner } from "~/components/custom/loadingspinner" -import { Badge } from "~/components/ui/badge" -import { Button } from "~/components/ui/button" +import type { z } from "zod" +import type { FormSchema } from "~/components/blocks/fertilizer/formschema" +import { Input } from "~/components/ui/input" +import { Label } from "~/components/ui/label" +import { Textarea } from "~/components/ui/textarea" +import { RadioGroup, RadioGroupItem } from "~/components/ui/radio-group" import { Card, CardContent, CardDescription, - CardFooter, CardHeader, CardTitle, } from "~/components/ui/card" import { - FormControl, - FormDescription, FormField, FormItem, + FormLabel, + FormControl, + FormDescription, FormMessage, } from "~/components/ui/form" -import { Input } from "~/components/ui/input" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "~/components/ui/select" -import type { Fertilizer } from "./columns" +import { cn } from "~/lib/utils" +import { RemixFormProvider } from "remix-hook-form" +import { Form } from "react-router" +import { Button } from "../../ui/button" + +// Define types directly to avoid import issues +export type FertilizerParameters = keyof z.infer + +export interface FertilizerParameterDescriptionItem { + parameter: + | Exclude< + FertilizerParameters, + "p_type_manure" | "p_type_mineral" | "p_type_compost" + > + | "p_type" // Add p_type back for description purposes + unit: string + type: "numeric" | "enum" | "date" | "text" + name: string + description: string + category: + | "general" + | "primary" + | "secondary" + | "trace" + | "heavy_metals" + | "physical" + min?: number + max?: number + options?: unknown +} + +export type FertilizerParameterDescription = + FertilizerParameterDescriptionItem[] + +type FormSchemaKeys = keyof z.infer + +type FertilizerFormNewProps = { + fertilizerParameters: FertilizerParameterDescription + form: any +} export function FertilizerForm({ - fertilizer, + fertilizerParameters, form, - editable, - farm, -}: { - fertilizer: Fertilizer - form: UseFormReturn< - z.infer, - ZodType, - undefined - > - editable: boolean - farm: { - b_id_farm: string - b_name_farm: string +}: FertilizerFormNewProps) { + const categories = [ + { + name: "general", + title: "Algemeen", + description: "Algemene informatie over de meststof.", + }, + { + name: "physical", + title: "Fysieke eigenschappen", + description: "Fysieke eigenschappen van de meststof.", + }, + { + name: "primary", + title: "Primaire nutriënten", + description: "Belangrijkste nutriënten (N, P, K).", + }, + { + name: "secondary", + title: "Secundaire nutriënten", + description: "Secundaire nutriënten (S, Mg, Ca, Na).", + }, + { + name: "trace", + title: "Sporenelementen", + description: "Sporenelementen (Cu, Zn, Co, etc.).", + }, + ] + + const getParameterInput = (param: FertilizerParameterDescriptionItem) => { + return ( + ( + + + {param.name} {param.unit && `(${param.unit})`} + + + {param.parameter === "p_description" ? ( +