Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d9ab3cd
feat: improved the ui/ux and code quality of the fertilizers pages
SvenVw Mar 11, 2026
4396e3a
refactor: remove unused imports
SvenVw Mar 11, 2026
ed8e24e
fix: Render the array-level error for application methods.
SvenVw Mar 11, 2026
e1202ad
fix: Preserve returnUrl in the “Gebruik als sjabloon” link.
SvenVw Mar 11, 2026
bddd849
refactor: Forward rvoToType
SvenVw Mar 11, 2026
824a1b6
fix: Add useEffect to reset form when the fertilizer changes
SvenVw Mar 11, 2026
63c718c
fix: Make the clickable row keyboard reachable
SvenVw Mar 11, 2026
2fb53de
fix: Add missing parameters to Fertilizer type
SvenVw Mar 11, 2026
21880cf
refactor: Extract the RVO metadata assembly into one helper
SvenVw Mar 11, 2026
6985784
Merge branch 'development' into FDM500
SvenVw Mar 11, 2026
9211e46
refactor: implement feedback
SvenVw Mar 11, 2026
570c28a
fix: Don't make the <tr> itself the link target
SvenVw Mar 11, 2026
27cdca3
tests: fix types
SvenVw Mar 11, 2026
d89c6fb
refactor: remove unused variable
SvenVw Mar 11, 2026
31e46c5
refactor: add dependency to useEffect
SvenVw Mar 11, 2026
445ad68
Update fdm-app/app/routes/farm.$b_id_farm.fertilizers.new._index.tsx
SvenVw Mar 11, 2026
a48049f
refactor: Render the add CTA as a single interactive element
SvenVw Mar 11, 2026
16dc1a6
fix: Keep one always-visible navigation column.
SvenVw Mar 11, 2026
a0f2692
Merge branch 'development' into FDM500
SvenVw Mar 12, 2026
95f1829
fix: syntax error
SvenVw Mar 12, 2026
150034e
fix: rerender state
SvenVw Mar 12, 2026
53e18cd
Merge branch 'development' into FDM500
BoraIneviNMI Mar 20, 2026
cdfa5b5
fix: lockfile
SvenVw Mar 20, 2026
dd4cfa5
refactor: after adjusting fertilizer redirect to fertilizers list
SvenVw Mar 20, 2026
5470dc6
Merge branch 'development' into FDM500
SvenVw Mar 20, 2026
ea2e174
fix: units should be lowercase
SvenVw Mar 20, 2026
e694058
Merge branch 'FDM500' of https://github.com/nmi-agro/fdm into FDM500
SvenVw Mar 20, 2026
a0aad7a
fix: do not show decimals
SvenVw Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/fertilizer-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# @nmi-agro/fdm-app

## Fertilizer Pages UI/UX & Code Quality Improvements

### UI/UX Enhancements

- **Fertilizer Table:** Added all nutrient columns (DS, OS, MgO, CaO, Na₂O, SO₃, trace elements, and N-efficiency). Columns are hidden by default and manageable via a new "Kolommen" dropdown.
- **Searchable Catalogue:** Replaced the card grid with a searchable catalogue picker. It distinguishes between standard and custom fertilizers with subtle tagging and uses color-coded RVO category badges. The search index now includes RVO category names.
- **Modernized Form:** Migrated the fertilizer form to the latest `Field` component system. Implemented a 3-column grid for numeric fields and optimized the application method section.
- **Improved Sidebar ("Samenvatting"):** Added a professional summary sidebar that calculates "Werkzame N" live and displays key analytics in a clean monochromatic format.
- **Mobile Optimizations:** Added a smart floating action bar on mobile that hides when the main save button is visible. Reduced excessive padding across all fertilizer pages.
- **Better Navigation:** Entire table rows are now clickable. Fixed the "Make a copy" feature (now "Gebruik als sjabloon") with reliable pathing and friendly guidance.

### Code Quality

- **Refactoring:** Extracted shared logic for defaults and payload building into `utils.ts`.
- **Component Consolidation:** Merged duplicate form pages into a single reusable `FarmNewFertilizerBlock`.
- **Server/Client Safety:** Correctly separated server-side action logic from client-side utility helpers to prevent build errors.
- **Consistency:** Aligned typography, card styling, and badge usage with the rest of the application.
5 changes: 5 additions & 0 deletions .changeset/neat-adults-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nmi-agro/fdm-core": patch
---

Add missing parameters to Fertilizer type
16 changes: 16 additions & 0 deletions .changeset/tall-singers-stand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@nmi-agro/fdm-app": minor
---

Improved the UI/UX and code quality of the fertilizers pages with the following:

- **Fertilizer Table:** Added all nutrient columns (DS, OS, MgO, CaO, Na₂O, SO₃, trace elements, and N-efficiency). Columns are hidden by default and manageable via a new "Kolommen" dropdown.
- **Searchable Catalogue:** Replaced the card grid with a searchable catalogue picker. It distinguishes between standard and custom fertilizers with subtle tagging and uses color-coded RVO category badges. The search index now includes RVO category names.
- **Modernized Form:** Migrated the fertilizer form to the latest `Field` component system. Implemented a 3-column grid for numeric fields and optimized the application method section.
- **Improved Sidebar ("Samenvatting"):** Added a professional summary sidebar that calculates "Werkzame N" live and displays key analytics in a clean monochromatic format.
- **Mobile Optimizations:** Added a smart floating action bar on mobile that hides when the main save button is visible. Reduced excessive padding across all fertilizer pages.
- **Better Navigation:** Entire table rows are now clickable. Fixed the "Make a copy" feature (now "Gebruik als sjabloon") with reliable pathing and friendly guidance.
- **Refactoring:** Extracted shared logic for defaults and payload building into `utils.ts`.
- **Component Consolidation:** Merged duplicate form pages into a single reusable `FarmNewFertilizerBlock`.
- **Server/Client Safety:** Correctly separated server-side action logic from client-side utility helpers to prevent build errors.
- **Consistency:** Aligned typography, card styling, and badge usage with the rest of the application.
4 changes: 2 additions & 2 deletions fdm-app/app/components/blocks/farm/farm-title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface FarmTitleProps {

export function FarmTitle({ title, description, action }: FarmTitleProps) {
return (
<div className="space-y-6 p-4 md:px-6 md:py-8 pb-0">
<div className="space-y-6 p-4 md:px-8 md:py-8 pb-0">
<div className="flex flex-col xl:flex-row xl:items-center gap-4">
<div className="space-y-0.5 min-w-0 flex-1">
<h2 className="text-2xl font-bold tracking-tight truncate xl:whitespace-normal">
Expand All @@ -39,7 +39,7 @@ export function FarmTitle({ title, description, action }: FarmTitleProps) {

export function FarmTitleSkeleton() {
return (
<div className="space-y-6 p-4 md:px-6 md:py-8 pb-0">
<div className="space-y-6 p-4 md:px-8 md:py-8 pb-0">
<div className="flex flex-col xl:flex-row xl:items-center gap-4">
<div className="space-y-0.5 min-w-0 flex-1">
<Skeleton className="h-8 w-50 md:w-64" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export function FertilizerApplicationCard({
savedFormValues,
editedFertilizerApplication,
fieldFertilizerFormStore,
calendar,
])

useEffect(() => {
Expand Down Expand Up @@ -176,7 +177,7 @@ export function FertilizerApplicationCard({
Toevoegen
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[800px]">
<DialogContent className="sm:max-w-200">
<DialogHeader>
<DialogTitle className="flex flex-row items-center justify-between mr-4">
{editedFertilizerApplication
Expand Down
267 changes: 232 additions & 35 deletions fdm-app/app/components/blocks/fertilizer/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ColumnDef } from "@tanstack/react-table"
import { ArrowRight } from "lucide-react"
import { NavLink } from "react-router-dom"
import { Pencil } from "lucide-react"
import { Badge } from "~/components/ui/badge"
import {
Tooltip,
Expand All @@ -13,53 +12,270 @@ import { DataTableColumnHeader } from "./column-header"
export type Fertilizer = {
p_id: string
p_name_nl: string
p_dm?: number | null
p_density?: number | null
p_om?: number | null
p_n_rt?: number | null
p_p_rt?: number | null
p_k_rt?: number | null
p_mg_rt?: number | null
p_ca_rt?: number | null
p_na_rt?: number | null
p_s_rt?: number | null
p_cu_rt?: number | null
p_zn_rt?: number | null
p_b_rt?: number | null
p_mn_rt?: number | null
p_ni_rt?: number | null
p_fe_rt?: number | null
p_mo_rt?: number | null
p_co_rt?: number | null
p_as_rt?: number | null
p_cd_rt?: number | null
p_cr_rt?: number | null
p_cr_vi?: number | null
p_pb_rt?: number | null
p_hg_rt?: number | null
p_cl_rt?: number | null
p_eoc?: number | null
p_n_wc?: number | null
p_type_rvo?: string | null
p_type_rvo_label?: string | null
p_type?: "manure" | "compost" | "mineral" | null
p_eoc?: number | null
p_source?: string
p_n_wc?: number | null
p_om?: number | null
p_s_rt?: number | null
p_ca_rt?: number | null
p_mg_rt?: number | null
is_custom?: boolean
}

function formatNumber(value: number | null | undefined): string {
if (value === null || value === undefined) return "-"
return new Intl.NumberFormat("nl-NL", {
maximumFractionDigits: 2,
}).format(value)
}

function formatPercent(value: number | null | undefined): string {
if (value === null || value === undefined) return "-"
return new Intl.NumberFormat("nl-NL", {
style: "percent",
maximumFractionDigits: 0,
}).format(value)
}

export const columns: ColumnDef<Fertilizer>[] = [
// {
// accessorKey: "p_id",
// header: "ID",
// },
{
accessorKey: "p_name_nl",
header: "Naam",
cell: ({ row }) => {
const isCustom = row.original.is_custom
return (
<div className="flex items-center gap-2">
<span className="font-medium">
{row.original.p_name_nl}
</span>
{isCustom && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge
variant="outline"
className="px-1 py-0 h-5"
>
<Pencil className="h-3 w-3 text-muted-foreground" />
</Badge>
</TooltipTrigger>
<TooltipContent>
<p>Eigen meststof</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
)
},
},
{
accessorKey: "p_n_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="N" />
return <DataTableColumnHeader column={column} title="N (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_n_rt),
},
{
accessorKey: "p_n_wc",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="N-werk." />
},
cell: ({ row }) => formatPercent(row.original.p_n_wc),
},
{
accessorKey: "p_p_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="P2O5" />
return <DataTableColumnHeader column={column} title="P₂O₅ (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_p_rt),
},
{
accessorKey: "p_k_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="K2O" />
return <DataTableColumnHeader column={column} title="K₂O (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_k_rt),
},
{
accessorKey: "p_dm",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="DS (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_dm),
},
{
accessorKey: "p_om",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="OS (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_om),
},
{
accessorKey: "p_mg_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="MgO (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_mg_rt),
},
{
accessorKey: "p_ca_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="CaO (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_ca_rt),
},
{
accessorKey: "p_na_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Na₂O (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_na_rt),
},
{
accessorKey: "p_s_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="SO₃ (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_s_rt),
},
{
accessorKey: "p_cu_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Cu (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_cu_rt),
},
{
accessorKey: "p_zn_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Zn (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_zn_rt),
},
{
accessorKey: "p_b_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="B (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_b_rt),
},
{
accessorKey: "p_mn_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Mn (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_mn_rt),
},
{
accessorKey: "p_ni_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Ni (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_ni_rt),
},
{
accessorKey: "p_fe_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Fe (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_fe_rt),
},
{
accessorKey: "p_mo_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Mo (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_mo_rt),
},
{
accessorKey: "p_co_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Co (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_co_rt),
},
{
accessorKey: "p_as_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="As (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_as_rt),
},
{
accessorKey: "p_cd_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Cd (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_cd_rt),
},
{
accessorKey: "p_cr_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Cr (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_cr_rt),
},
{
accessorKey: "p_cr_vi",
header: ({ column }) => {
return (
<DataTableColumnHeader column={column} title="Cr-VI (mg/kg)" />
)
},
cell: ({ row }) => formatNumber(row.original.p_cr_vi),
},
{
accessorKey: "p_pb_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Pb (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_pb_rt),
},
{
accessorKey: "p_hg_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Hg (mg/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_hg_rt),
},
{
accessorKey: "p_cl_rt",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Cl (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_cl_rt),
},
{
accessorKey: "p_eoc",
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="EOC" />
return <DataTableColumnHeader column={column} title="EOC (g/kg)" />
},
cell: ({ row }) => formatNumber(row.original.p_eoc),
},
{
accessorKey: "p_type_rvo",
Expand Down Expand Up @@ -117,23 +333,4 @@ export const columns: ColumnDef<Fertilizer>[] = [
)
},
},
{
accessorKey: "Details",
cell: ({ row }) => {
const fertilizer = row.original

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className=" text-sm text-muted-foreground/70">
<NavLink to={`./${fertilizer.p_id}`}>
<ArrowRight className="text-sm" />
</NavLink>
</TooltipTrigger>
<TooltipContent>{`Bekijk details over ${fertilizer.p_name_nl}`}</TooltipContent>
</Tooltip>
</TooltipProvider>
)
},
},
]
Loading
Loading