From 7bf4b6c3189dce036ec4d904f37c4d40885dcf43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 19 Aug 2025 14:32:26 +0200 Subject: [PATCH 01/14] Return the handleActionError instead of throwing it --- fdm-app/app/lib/form.ts | 2 +- .../routes/farm.$b_id_farm.$calendar.field.$b_id.atlas.tsx | 4 ++-- ...eld.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx | 2 +- ....$calendar.field.$b_id.cultivation.$b_lu.harvest.new.tsx | 2 +- ...m.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx | 2 +- .../farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx | 2 +- .../farm.$b_id_farm.$calendar.field.$b_id.fertilizer.tsx | 2 +- .../farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx | 2 +- ...$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx | 2 +- ...alendar.field.$b_id.soil.analysis.new.$analysis_type.tsx | 2 +- ..._farm.$calendar.field.$b_id.soil.analysis.new.upload.tsx | 2 +- fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx | 2 +- fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx | 2 +- .../app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx | 2 +- .../app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx | 2 +- fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx | 2 +- fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx | 2 +- fdm-app/app/routes/farm.$b_id_farm.tsx | 2 +- .../app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx | 2 +- ...m.$calendar.cultivations.$b_lu_catalogue.crop._index.tsx | 2 +- ...ations.$b_lu_catalogue.crop.harvest.$b_id_harvesting.tsx | 2 +- ...lendar.cultivations.$b_lu_catalogue.crop.harvest.new.tsx | 2 +- ...ate.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx | 2 +- ...farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx | 2 +- ....$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx | 2 +- ..._id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx | 2 +- .../app/routes/farm.create.$b_id_farm.$calendar.upload.tsx | 2 +- fdm-app/app/routes/farm.create._index.tsx | 2 +- fdm-app/app/routes/logout.tsx | 2 +- fdm-app/app/routes/organization.$slug.tsx | 6 +++--- fdm-app/app/routes/organization.invitations.tsx | 2 +- fdm-app/app/routes/organization.new.tsx | 2 +- 32 files changed, 35 insertions(+), 35 deletions(-) diff --git a/fdm-app/app/lib/form.ts b/fdm-app/app/lib/form.ts index 431b567e1..0f5975570 100644 --- a/fdm-app/app/lib/form.ts +++ b/fdm-app/app/lib/form.ts @@ -70,6 +70,6 @@ export async function extractFormValuesFromRequest( return parsedData.data as z.infer } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.atlas.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.atlas.tsx index 54869d4af..22e23ac14 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.atlas.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.atlas.tsx @@ -88,7 +88,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { mapboxStyle: mapboxStyle, } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } @@ -169,6 +169,6 @@ export async function action({ request, params }: ActionFunctionArgs) { throw new Error("Missing field ID.") } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx index 58430ae6e..12e0b7663 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.$b_id_harvesting.tsx @@ -237,6 +237,6 @@ export async function action({ request, params }: ActionFunctionArgs) { ) } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.new.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.new.tsx index 05ca394f6..8a9bef677 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.new.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.harvest.new.tsx @@ -113,6 +113,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: "Oogst succesvol toegevoegd! 🎉", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx index 34ce332f3..54149ff79 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.$b_lu.tsx @@ -266,6 +266,6 @@ export async function action({ request, params }: ActionFunctionArgs) { ) } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx index 2dc597101..3ac3c7d63 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.cultivation.tsx @@ -228,6 +228,6 @@ export async function action({ request, params }: ActionFunctionArgs) { }) } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer.tsx index f97d614da..ef6fda48e 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer.tsx @@ -231,6 +231,6 @@ export async function action({ request, params }: ActionFunctionArgs) { }) } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx index dc613d912..c266ea9e8 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx @@ -225,6 +225,6 @@ export async function action({ request, params }: ActionFunctionArgs) { statusText: "Method not allowed", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx index 159030a65..7d7c41821 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.$a_id.tsx @@ -202,6 +202,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: "Bodemanalyse is bijgewerkt! 🎉", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.$analysis_type.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.$analysis_type.tsx index f8dcdc663..f7d93bfa1 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.$analysis_type.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.$analysis_type.tsx @@ -199,6 +199,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: "Bodemanalyse is toegevoegd! 🎉", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.upload.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.upload.tsx index e8f50ac44..ab47d4893 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.upload.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil.analysis.new.upload.tsx @@ -210,6 +210,6 @@ export async function action({ request, params }: ActionFunctionArgs) { ) } - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx index dbec73054..8b501c183 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx @@ -472,6 +472,6 @@ export async function action({ request, params }: ActionFunctionArgs) { }, ) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx index 039980b10..20cce1832 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx @@ -262,6 +262,6 @@ export async function action({ request, params }: ActionFunctionArgs) { { message: "Meststof is bijgewerkt! 🎉" }, ) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx index a7e9b8535..b335ec005 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx @@ -275,6 +275,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: `${formValues.p_name_nl} is toegevoegd! 🎉`, }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx index c50860dba..2f22cf543 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx @@ -329,6 +329,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: `${formValues.p_name_nl} is toegevoegd! 🎉`, }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx index e79c7c0dc..65742f0c6 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.settings.derogation.tsx @@ -81,7 +81,7 @@ export async function action({ request, params }: ActionFunctionArgs) { `Het is niet gelukt derogatie voor ${year} aan te passen.`, ) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx index ecaa41a09..b9d509975 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.settings.properties.tsx @@ -297,7 +297,7 @@ export async function action({ request, params }: ActionFunctionArgs) { message: `${formValues.b_name_farm} is bijgewerkt! 🎉`, }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.tsx b/fdm-app/app/routes/farm.$b_id_farm.tsx index e5758d7b6..45fb18ad8 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.tsx @@ -45,6 +45,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { session, } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx index 88ca355b7..ffe73382e 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx @@ -488,6 +488,6 @@ export async function action({ request, params }: ActionFunctionArgs) { }, ) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop._index.tsx index 3a39cf6fb..0d1c6c408 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop._index.tsx @@ -272,6 +272,6 @@ export async function action({ request, params }: ActionFunctionArgs) { ) } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.$b_id_harvesting.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.$b_id_harvesting.tsx index 65fe90b70..d3b15a1a4 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.$b_id_harvesting.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.$b_id_harvesting.tsx @@ -320,6 +320,6 @@ export async function action({ request, params }: ActionFunctionArgs) { throw new Error("Invalid request method") } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.new.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.new.tsx index d807527ed..001085ad9 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.new.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.$b_lu_catalogue.crop.harvest.new.tsx @@ -149,6 +149,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: "Oogst succesvol toegevoegd! 🎉", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx index 99817a2c7..43cddad4a 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx @@ -252,6 +252,6 @@ export async function action({ request, params }: ActionFunctionArgs) { } throw new Error(`${request.method} is not supported`) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx index 0912e2943..ee1b63716 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx @@ -521,6 +521,6 @@ export async function action({ request, params }: ActionFunctionArgs) { throw new Error("invalid method") } } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx index 8ca187cfe..afc7baf9d 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx @@ -192,6 +192,6 @@ export async function action({ request, params }: ActionFunctionArgs) { message: "Bodemanalyse is toegevoegd! 🎉", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx index 5c973717d..1acad95e0 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx @@ -210,6 +210,6 @@ export async function action({ request, params }: ActionFunctionArgs) { ) } - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx index c2d06fe41..95edf6394 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx @@ -265,6 +265,6 @@ export async function action({ request, params }: ActionFunctionArgs) { }, ) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/farm.create._index.tsx b/fdm-app/app/routes/farm.create._index.tsx index 6c33bec68..a6595ce46 100644 --- a/fdm-app/app/routes/farm.create._index.tsx +++ b/fdm-app/app/routes/farm.create._index.tsx @@ -428,6 +428,6 @@ export async function action({ request }: ActionFunctionArgs) { message: "Bedrijf is toegevoegd! 🎉 Selecteer nu de importmethode.", }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/logout.tsx b/fdm-app/app/routes/logout.tsx index e079d092a..c8afb5237 100644 --- a/fdm-app/app/routes/logout.tsx +++ b/fdm-app/app/routes/logout.tsx @@ -29,6 +29,6 @@ export async function action({ request }: ActionFunctionArgs) { }) return redirect("/signin") } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/organization.$slug.tsx b/fdm-app/app/routes/organization.$slug.tsx index c9e93bdd6..5a1fb0632 100644 --- a/fdm-app/app/routes/organization.$slug.tsx +++ b/fdm-app/app/routes/organization.$slug.tsx @@ -380,7 +380,7 @@ const FormSchema = z.object({ export async function action({ request, params }: ActionFunctionArgs) { try { if (!params.slug) { - throw handleActionError("not found: organization") + return handleActionError("not found: organization") } const formValues = await extractFormValuesFromRequest( request, @@ -393,7 +393,7 @@ export async function action({ request, params }: ActionFunctionArgs) { session.user.id, ) if (!organization) { - throw handleActionError("not found: organization") + return handleActionError("not found: organization") } if (formValues.intent === "invite_user") { @@ -473,6 +473,6 @@ export async function action({ request, params }: ActionFunctionArgs) { } throw new Error("invalid intent") } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/organization.invitations.tsx b/fdm-app/app/routes/organization.invitations.tsx index d5c357512..125c3bdab 100644 --- a/fdm-app/app/routes/organization.invitations.tsx +++ b/fdm-app/app/routes/organization.invitations.tsx @@ -239,6 +239,6 @@ export async function action({ request }: ActionFunctionArgs) { } throw new Error("invalid intent") } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } diff --git a/fdm-app/app/routes/organization.new.tsx b/fdm-app/app/routes/organization.new.tsx index ce09d7417..957b6e032 100644 --- a/fdm-app/app/routes/organization.new.tsx +++ b/fdm-app/app/routes/organization.new.tsx @@ -260,7 +260,7 @@ export async function action({ request }: ActionFunctionArgs) { message: `Organisatie ${formValues.name} is aangemaakt! 🎉`, }) } catch (error) { - throw handleActionError(error) + return handleActionError(error) } } From 32c293836a408d2bc9cdc8197e446987fb57866a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 19 Aug 2025 14:50:17 +0200 Subject: [PATCH 02/14] Make the 404 page visually distinct --- fdm-app/app/components/custom/error.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/fdm-app/app/components/custom/error.tsx b/fdm-app/app/components/custom/error.tsx index 4206dc1e9..916aa2568 100644 --- a/fdm-app/app/components/custom/error.tsx +++ b/fdm-app/app/components/custom/error.tsx @@ -57,13 +57,20 @@ export function ErrorBlock({ } return (
-
- A red tractor doing a wheelie -
+ {status === 404 && ( +
+ 404 +
+ )} + {status !== 404 && ( +
+ A red tractor doing a wheelie +
+ )}

{status === 404 ? "Aii, deze pagina bestaat niet." From c6801da1ad2ea68e13f2c5536c656677f083b7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Tue, 19 Aug 2025 14:57:45 +0200 Subject: [PATCH 03/14] Move ErrorBoundary into its own file called InlineErrorBoundary --- .../custom/inline-error-boundary.tsx | 87 ++++++++++++++++++ fdm-app/app/root.tsx | 88 +------------------ 2 files changed, 90 insertions(+), 85 deletions(-) create mode 100644 fdm-app/app/components/custom/inline-error-boundary.tsx diff --git a/fdm-app/app/components/custom/inline-error-boundary.tsx b/fdm-app/app/components/custom/inline-error-boundary.tsx new file mode 100644 index 000000000..8bb0312cd --- /dev/null +++ b/fdm-app/app/components/custom/inline-error-boundary.tsx @@ -0,0 +1,87 @@ +import * as Sentry from "@sentry/react-router" +import { isRouteErrorResponse, redirect, useLocation } from "react-router" +import type { Route } from "../../+types/root" +import { ErrorBlock } from "./error" + +/** + * Renders an error boundary that handles and displays error information based on the provided error. + * + * This component distinguishes between route error responses and generic errors: + * - For route errors: + * - Redirects to the signin page if the error status is 401. + * - Renders a 404 error block for client errors with status 400, 403, or 404. + * - Logs other route errors to the error tracking service and renders an error block reflecting the specific status. + * - For generic Error instances, it logs the error and renders a 500 error block with the error message and stack trace. + * - If the error is null, no error UI is rendered. + * - For any other cases, it logs the error and displays an error block with a 500 status and a generic message. + * + * @param error - The error encountered during route processing, either as a route error response or a generic Error. + */ +export function InlineErrorBoundary({ error }: Route.ErrorBoundaryProps) { + const location = useLocation() + const page = location.pathname + const timestamp = new Date().toISOString() + + if (isRouteErrorResponse(error)) { + // Redirect to signin page if authentication is not provided + if (error.status === 401) { + // Get the current path the user tried to access + const currentPath = + location.pathname + location.search + location.hash + // Construct the sign-in URL with the redirectTo parameter + const signInUrl = `./signin?redirectTo=${encodeURIComponent(currentPath)}` + // Throw the redirect response to be caught by React Router + throw redirect(signInUrl) + } + + const clientErrors = [400, 403, 404] + if (clientErrors.includes(error.status)) { + return ( + + ) + } + + Sentry.captureException(error) + return ( + + ) + } + if (error instanceof Error) { + Sentry.captureException(error) + return ( + + ) + } + if (error === null) { + return null + } + + Sentry.captureException(error) + return ( + + ) +} diff --git a/fdm-app/app/root.tsx b/fdm-app/app/root.tsx index 23409bf6f..7f376f11a 100644 --- a/fdm-app/app/root.tsx +++ b/fdm-app/app/root.tsx @@ -1,15 +1,12 @@ -import * as Sentry from "@sentry/react-router" import mapBoxStyle from "mapbox-gl/dist/mapbox-gl.css?url" import posthog from "posthog-js" import { useEffect } from "react" import type { LinksFunction, LoaderFunctionArgs } from "react-router" import { data, - isRouteErrorResponse, Links, Meta, Outlet, - redirect, Scripts, ScrollRestoration, useLoaderData, @@ -17,8 +14,8 @@ import { } from "react-router" import { getToast } from "remix-toast" import { toast as notify } from "sonner" +import { InlineErrorBoundary } from "@/app/components/custom/inline-error-boundary" import { Banner } from "~/components/custom/banner" -import { ErrorBlock } from "~/components/custom/error" import { Toaster } from "~/components/ui/sonner" import { clientConfig } from "~/lib/config" import { useChangelogStore } from "~/store/changelog" @@ -175,85 +172,6 @@ export default function App() { return } -/** - * Renders an error boundary that handles and displays error information based on the provided error. - * - * This component distinguishes between route error responses and generic errors: - * - For route errors: - * - Redirects to the signin page if the error status is 401. - * - Renders a 404 error block for client errors with status 400, 403, or 404. - * - Logs other route errors to the error tracking service and renders an error block reflecting the specific status. - * - For generic Error instances, it logs the error and renders a 500 error block with the error message and stack trace. - * - If the error is null, no error UI is rendered. - * - For any other cases, it logs the error and displays an error block with a 500 status and a generic message. - * - * @param error - The error encountered during route processing, either as a route error response or a generic Error. - */ -export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - const location = useLocation() - const page = location.pathname - const timestamp = new Date().toISOString() - - if (isRouteErrorResponse(error)) { - // Redirect to signin page if authentication is not provided - if (error.status === 401) { - // Get the current path the user tried to access - const currentPath = - location.pathname + location.search + location.hash - // Construct the sign-in URL with the redirectTo parameter - const signInUrl = `./signin?redirectTo=${encodeURIComponent(currentPath)}` - // Throw the redirect response to be caught by React Router - throw redirect(signInUrl) - } - - const clientErrors = [400, 403, 404] - if (clientErrors.includes(error.status)) { - return ( - - ) - } - - Sentry.captureException(error) - return ( - - ) - } - if (error instanceof Error) { - Sentry.captureException(error) - return ( - - ) - } - if (error === null) { - return null - } - - Sentry.captureException(error) - return ( - - ) +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + return } From 08d1e7fa898e3c4c142e101c37d5529912ac4d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 20 Aug 2025 14:08:25 +0200 Subject: [PATCH 04/14] Add page-specific error boundaries to pages after farm creation --- fdm-app/app/components/blocks/header/farm.tsx | 11 +- .../components/blocks/header/fertilizer.tsx | 58 ++++++-- .../app/components/blocks/header/field.tsx | 10 +- ....$b_id_farm.$calendar.balance.nitrogen.tsx | 28 ++++ .../farm.$b_id_farm.$calendar.field.$b_id.tsx | 125 ++++++++++++++---- .../farm.$b_id_farm.$calendar.norms.tsx | 34 ++++- ...d_farm.$calendar.nutrient_advice.$b_id.tsx | 6 + .../routes/farm.$b_id_farm.fertilizers.tsx | 31 +++++ fdm-app/app/routes/farm.$b_id_farm.tsx | 24 ++++ fdm-app/app/routes/farm._index.tsx | 22 ++- fdm-app/app/store/farm-field-options.tsx | 62 +++++++++ fdm-app/app/store/fertilizer-options.tsx | 0 12 files changed, 364 insertions(+), 47 deletions(-) create mode 100644 fdm-app/app/routes/farm.$b_id_farm.fertilizers.tsx create mode 100644 fdm-app/app/store/farm-field-options.tsx create mode 100644 fdm-app/app/store/fertilizer-options.tsx diff --git a/fdm-app/app/components/blocks/header/farm.tsx b/fdm-app/app/components/blocks/header/farm.tsx index 594d30b1b..6217931c3 100644 --- a/fdm-app/app/components/blocks/header/farm.tsx +++ b/fdm-app/app/components/blocks/header/farm.tsx @@ -1,5 +1,7 @@ import { ChevronDown } from "lucide-react" +import { useEffect } from "react" import { NavLink, useLocation } from "react-router" +import { useFarmFieldOptionsStore } from "@/app/store/farm-field-options" import { BreadcrumbItem, BreadcrumbLink, @@ -20,6 +22,13 @@ export function HeaderFarm({ farmOptions: HeaderFarmOption[] }) { const location = useLocation() + const farmFieldOptionsStore = useFarmFieldOptionsStore() + useEffect(() => { + if (farmOptions && farmOptions.length > 0) { + farmFieldOptionsStore.setFarmOptions(farmOptions) + } + }, [farmOptions, farmFieldOptionsStore.setFarmOptions]) + const currentPath = String(location.pathname) return ( @@ -75,7 +84,7 @@ export function HeaderFarm({ ) } -type HeaderFarmOption = { +export type HeaderFarmOption = { b_id_farm: string b_name_farm: string | undefined | null } diff --git a/fdm-app/app/components/blocks/header/fertilizer.tsx b/fdm-app/app/components/blocks/header/fertilizer.tsx index 84f27b537..98577ce94 100644 --- a/fdm-app/app/components/blocks/header/fertilizer.tsx +++ b/fdm-app/app/components/blocks/header/fertilizer.tsx @@ -1,5 +1,7 @@ import { ChevronDown } from "lucide-react" +import { useEffect } from "react" import { NavLink, useLocation } from "react-router" +import { create } from "zustand" import { BreadcrumbItem, BreadcrumbLink, @@ -12,16 +14,44 @@ import { DropdownMenuTrigger, } from "~/components/ui/dropdown-menu" +const useFertilizerOptionsStore = create<{ + fertilizerOptions: HeaderFertilizerOption[] | undefined + setFertilizerOptions(fertilizerOptions: HeaderFertilizerOption[]): void +}>((set) => ({ + fertilizerOptions: undefined, + setFertilizerOptions(fertilizerOptions) { + set({ fertilizerOptions }) + }, +})) + export function HeaderFertilizer({ b_id_farm, p_id, - fertilizerOptions, + fertilizerOptions: pFertilizerOptions, }: { b_id_farm: string p_id: string | undefined - fertilizerOptions: HeaderFertilizerOption[] + fertilizerOptions: HeaderFertilizerOption[] | undefined }) { const location = useLocation() + const fertilizerOptionsStore = useFertilizerOptionsStore() + + useEffect(() => { + if ( + pFertilizerOptions && + pFertilizerOptions !== fertilizerOptionsStore.fertilizerOptions + ) { + fertilizerOptionsStore.setFertilizerOptions(pFertilizerOptions) + } + }, [ + pFertilizerOptions, + fertilizerOptionsStore.fertilizerOptions, + fertilizerOptionsStore.setFertilizerOptions, + ]) + + const fertilizerOptions = + pFertilizerOptions ?? fertilizerOptionsStore.fertilizerOptions + const currentPath = String(location.pathname) return ( @@ -32,7 +62,7 @@ export function HeaderFertilizer({ Meststof - {fertilizerOptions.length > 0 ? ( + {fertilizerOptions && fertilizerOptions.length > 0 ? ( <> @@ -70,16 +100,18 @@ export function HeaderFertilizer({ ) : ( - <> - - - - Nieuwe meststof - - - + fertilizerOptions && ( + <> + + + + Nieuwe meststof + + + + ) )} ) diff --git a/fdm-app/app/components/blocks/header/field.tsx b/fdm-app/app/components/blocks/header/field.tsx index ec91b9f20..8c91d6986 100644 --- a/fdm-app/app/components/blocks/header/field.tsx +++ b/fdm-app/app/components/blocks/header/field.tsx @@ -1,6 +1,8 @@ import { ChevronDown } from "lucide-react" +import { useEffect } from "react" import { NavLink, useLocation } from "react-router" import { useCalendarStore } from "@/app/store/calendar" +import { useFarmFieldOptionsStore } from "@/app/store/farm-field-options" import { BreadcrumbItem, BreadcrumbLink, @@ -25,6 +27,12 @@ export function HeaderField({ const location = useLocation() const currentPath = String(location.pathname) const calendar = useCalendarStore((state) => state.calendar) + const farmFieldOptionsStore = useFarmFieldOptionsStore() + useEffect(() => { + if (fieldOptions && fieldOptions.length > 0) { + farmFieldOptionsStore.setFieldOptions(fieldOptions) + } + }, [fieldOptions, farmFieldOptionsStore.setFieldOptions]) return ( <> @@ -91,7 +99,7 @@ export function HeaderField({ ) } -type HeaderFieldOption = { +export type HeaderFieldOption = { b_id: string b_name: string | undefined | null } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx index fcf2d0678..e28fd139a 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx @@ -16,6 +16,9 @@ import { getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" +import type { Route } from "../+types/root" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -154,3 +157,28 @@ export default function FarmBalanceNitrogenBlock() { ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + + const farmFieldOptionsStore = useFarmFieldOptionsStore() + + return ( + +
+ + +
+
+ +
+
+ ) +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx index 7ab2391b7..9f597bb23 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx @@ -12,6 +12,7 @@ import { FarmTitle } from "~/components/blocks/farm/farm-title" import { Header } from "~/components/blocks/header/base" import { HeaderFarm } from "~/components/blocks/header/farm" import { HeaderField } from "~/components/blocks/header/field" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" import { SidebarInset } from "~/components/ui/sidebar" import { getSession } from "~/lib/auth.server" import { getCalendar, getTimeframe } from "~/lib/calendar" @@ -19,6 +20,49 @@ import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { useCalendarStore } from "~/store/calendar" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" +import type { Route } from "../+types/root" + +/** + * Return title and destination url for each sidebar item that should be available + * + * @param b_id_farm farm id + * @param calendar calendar year + * @param b_id field id + * @returns list of sidebar item data + */ +function getSidebarPageItems( + b_id_farm: string, + calendar: string, + b_id: string, +) { + return [ + { + to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/overview`, + title: "Overzicht", + }, + { + to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/cultivation`, + title: "Gewassen", + }, + { + to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/fertilizer`, + title: "Bemesting", + }, + { + to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/soil`, + title: "Bodem", + }, + { + to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/atlas`, + title: "Kaart", + }, + { + to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/delete`, + title: "Verwijderen", + }, + ] +} // Meta export const meta: MetaFunction = () => { @@ -128,32 +172,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { } // Create the items for sidebar page - const sidebarPageItems = [ - { - to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/overview`, - title: "Overzicht", - }, - { - to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/cultivation`, - title: "Gewassen", - }, - { - to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/fertilizer`, - title: "Bemesting", - }, - { - to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/soil`, - title: "Bodem", - }, - { - to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/atlas`, - title: "Kaart", - }, - { - to: `/farm/${b_id_farm}/${calendar}/field/${b_id}/delete`, - title: "Verwijderen", - }, - ] + const sidebarPageItems = getSidebarPageItems(b_id_farm, calendar, b_id) // Return user information from loader return { @@ -214,3 +233,57 @@ export default function FarmFieldIndex() { ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const { params } = props + + const cachedFarm = farmFieldOptionsStore.getFieldById(params.b_id_farm) + const cachedFarmName = cachedFarm + ? (cachedFarm.b_name ?? "Naam Onbekend") + : "Onbekend Perceel" + + return ( + +
+ + +
+ + {params.b_id_farm && params.calendar && params.b_id ? ( +
+ + + + +
+ ) : ( +
+ +
+ )} +
+ ) +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.tsx index 7a5c1b4a1..34a0c8166 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.tsx @@ -26,11 +26,14 @@ import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" -import { HeaderNorms } from "../components/blocks/header/norms" -import { Alert, AlertDescription } from "../components/ui/alert" -import { Button } from "../components/ui/button" -import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card" -import { Separator } from "../components/ui/separator" +import type { Route } from "../+types/root" +import { HeaderNorms } from "~/components/blocks/header/norms" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { Alert, AlertDescription } from "~/components/ui/alert" +import { Button } from "~/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card" +import { Separator } from "~/components/ui/separator" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -313,3 +316,24 @@ function Norms(loaderData: Awaited>) {

) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + + const farmFieldOptionsStore = useFarmFieldOptionsStore() + + return ( + +
+ + +
+
+ +
+
+ ) +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx index ef0605278..b47f2b4a9 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.$b_id.tsx @@ -28,6 +28,8 @@ import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" +import type { Route } from "../+types/root" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" // Meta export const meta: MetaFunction = () => { @@ -260,3 +262,7 @@ function FieldNutrientAdvice({ /> ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + return +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.tsx new file mode 100644 index 000000000..0628ae2c1 --- /dev/null +++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.tsx @@ -0,0 +1,31 @@ +import type { Route } from "../+types/root" +import { Header } from "~/components/blocks/header/base" +import { HeaderFarm } from "~/components/blocks/header/farm" +import { HeaderFertilizer } from "~/components/blocks/header/fertilizer" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + + const farmFieldOptionsStore = useFarmFieldOptionsStore() + + return ( + <> +
+ + +
+
+ +
+ + ) +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.tsx b/fdm-app/app/routes/farm.$b_id_farm.tsx index 45fb18ad8..f0bd9d7bd 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.tsx @@ -1,7 +1,13 @@ import { data, type LoaderFunctionArgs, type MetaFunction } from "react-router" +import { Header } from "~/components/blocks/header/base" +import { HeaderFarm } from "~/components/blocks/header/farm" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { SidebarInset } from "~/components/ui/sidebar" import { getSession } from "~/lib/auth.server" import { clientConfig } from "~/lib/config" import { handleActionError } from "~/lib/error" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" +import type { Route } from "../+types/root" // Meta export const meta: MetaFunction = () => { @@ -48,3 +54,21 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return handleActionError(error) } } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const { params } = props + + console.log(params) + return ( + +
+ +
+ +
+ ) +} diff --git a/fdm-app/app/routes/farm._index.tsx b/fdm-app/app/routes/farm._index.tsx index 2c182c72a..a613ed74f 100644 --- a/fdm-app/app/routes/farm._index.tsx +++ b/fdm-app/app/routes/farm._index.tsx @@ -25,7 +25,10 @@ import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { getTimeBasedGreeting } from "~/lib/greetings" -import { getCalendarSelection } from "../lib/calendar" +import { getCalendarSelection } from "~/lib/calendar" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { Route } from "../+types/root" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -409,3 +412,20 @@ export default function AppIndex() { ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const { params } = props + + return ( + +
+ +
+ +
+ ) +} diff --git a/fdm-app/app/store/farm-field-options.tsx b/fdm-app/app/store/farm-field-options.tsx new file mode 100644 index 000000000..c81277130 --- /dev/null +++ b/fdm-app/app/store/farm-field-options.tsx @@ -0,0 +1,62 @@ +import { create } from "zustand" +import type { HeaderFarmOption } from "~/components/blocks/header/farm" +import type { HeaderFieldOption } from "~/components/blocks/header/field" + +/** + * Cache for farm and field options, to be used in error boundaries only. + */ +interface FarmFieldOptionsStore { + /** + * Farm options cached when the farm breadcrumb was last rendered. + */ + farmOptions: HeaderFarmOption[] + /** + * Field options cached when the field breadcrumb was last rendered. + */ + fieldOptions: HeaderFieldOption[] + /** + * Sets the cached list of farms as provided by the farm breadcrumb. + * + * @param farmOptions the list of HeaderFarmOption + */ + setFarmOptions(farmOptions: HeaderFarmOption[]): void + /** + * Sets the cached list of fields as provided by the field breadcrumb. + * + * @param fieldOptions the list of HeaderFieldOption + */ + setFieldOptions(fieldOptions: HeaderFieldOption[]): void + /** + * Tries to get the cached farm with the given id, otherwise returns undefined. + * + * @param b_id_farm The farm id + * @returns the HeaderFarmOption if found otherwise undefined + */ + getFarmById(b_id_farm: string | undefined): HeaderFarmOption | undefined + /** + * Tries to get the cached field with the given id, otherwise returns undefined. + * + * @param b_id The field id + * @returns the HeaderFieldOption if found otherwise undefined + */ + getFieldById(b_id: string | undefined): HeaderFieldOption | undefined +} + +export const useFarmFieldOptionsStore = create( + (set) => ({ + farmOptions: [], + fieldOptions: [], + setFarmOptions(farmOptions) { + set({ farmOptions }) + }, + setFieldOptions(fieldOptions) { + set({ fieldOptions }) + }, + getFarmById(b_id_farm) { + return this.farmOptions.find((f) => f.b_id_farm === b_id_farm) + }, + getFieldById(b_id) { + return this.fieldOptions.find((f) => f.b_id === b_id) + }, + }), +) diff --git a/fdm-app/app/store/fertilizer-options.tsx b/fdm-app/app/store/fertilizer-options.tsx new file mode 100644 index 000000000..e69de29bb From e641841ea6adbeeba10a4bb13a3be158a009c0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 20 Aug 2025 15:59:42 +0200 Subject: [PATCH 05/14] Fix wrong field name query on the options store --- .../routes/farm.$b_id_farm.$calendar.field.$b_id.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx index 9f597bb23..f4417d422 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx @@ -238,9 +238,9 @@ export function ErrorBoundary(props: Route.ErrorBoundaryProps) { const farmFieldOptionsStore = useFarmFieldOptionsStore() const { params } = props - const cachedFarm = farmFieldOptionsStore.getFieldById(params.b_id_farm) - const cachedFarmName = cachedFarm - ? (cachedFarm.b_name ?? "Naam Onbekend") + const cachedField = farmFieldOptionsStore.getFieldById(params.b_id) + const cachedFieldName = cachedField + ? (cachedField.b_name ?? "Naam Onbekend") : "Onbekend Perceel" return ( @@ -257,7 +257,7 @@ export function ErrorBoundary(props: Route.ErrorBoundaryProps) { farmOptions={farmFieldOptionsStore.farmOptions} /> @@ -266,7 +266,7 @@ export function ErrorBoundary(props: Route.ErrorBoundaryProps) { {params.b_id_farm && params.calendar && params.b_id ? (
Date: Wed, 20 Aug 2025 16:04:38 +0200 Subject: [PATCH 06/14] Use get in FarmFieldOptionsStore --- fdm-app/app/store/farm-field-options.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fdm-app/app/store/farm-field-options.tsx b/fdm-app/app/store/farm-field-options.tsx index c81277130..942383811 100644 --- a/fdm-app/app/store/farm-field-options.tsx +++ b/fdm-app/app/store/farm-field-options.tsx @@ -43,7 +43,7 @@ interface FarmFieldOptionsStore { } export const useFarmFieldOptionsStore = create( - (set) => ({ + (set, get) => ({ farmOptions: [], fieldOptions: [], setFarmOptions(farmOptions) { @@ -53,10 +53,10 @@ export const useFarmFieldOptionsStore = create( set({ fieldOptions }) }, getFarmById(b_id_farm) { - return this.farmOptions.find((f) => f.b_id_farm === b_id_farm) + return get().farmOptions.find((f) => f.b_id_farm === b_id_farm) }, getFieldById(b_id) { - return this.fieldOptions.find((f) => f.b_id === b_id) + return get().fieldOptions.find((f) => f.b_id === b_id) }, }), ) From 3adf6772aba1ec041c74fea5df6a09e19d72d294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 20 Aug 2025 16:08:25 +0200 Subject: [PATCH 07/14] Delete fertilizer-options.tsx --- fdm-app/app/store/fertilizer-options.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fdm-app/app/store/fertilizer-options.tsx diff --git a/fdm-app/app/store/fertilizer-options.tsx b/fdm-app/app/store/fertilizer-options.tsx deleted file mode 100644 index e69de29bb..000000000 From 290588ab48cc657baac3b5988fbf9bd20c6b832b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 21 Aug 2025 14:42:09 +0200 Subject: [PATCH 08/14] Add error boundaries to farm create pages --- .../components/blocks/header/create-farm.tsx | 12 +++++- ...arm.create.$b_id_farm.$calendar.access.tsx | 42 +++++++++++++++++-- ...eate.$b_id_farm.$calendar.cultivations.tsx | 32 ++++++++++++++ ...reate.$b_id_farm.$calendar.fertilizers.tsx | 32 ++++++++++++++ ..._id_farm.$calendar.fields.$b_id._index.tsx | 12 +++++- ...lds.$b_id.soil.analysis.$analysis_type.tsx | 12 +++++- ...ndar.fields.$b_id.soil.analysis._index.tsx | 13 ++++-- ...ndar.fields.$b_id.soil.analysis.upload.tsx | 12 +++++- ...arm.create.$b_id_farm.$calendar.fields.tsx | 36 ++++++++++++++++ fdm-app/app/store/farm-field-options.tsx | 20 ++++++++- 10 files changed, 209 insertions(+), 14 deletions(-) diff --git a/fdm-app/app/components/blocks/header/create-farm.tsx b/fdm-app/app/components/blocks/header/create-farm.tsx index d5662dab2..995a151ba 100644 --- a/fdm-app/app/components/blocks/header/create-farm.tsx +++ b/fdm-app/app/components/blocks/header/create-farm.tsx @@ -1,4 +1,6 @@ -import { useLocation } from "react-router" +import { useEffect } from "react" +import { useLocation, useParams } from "react-router" +import { useFarmFieldOptionsStore } from "@/app/store/farm-field-options" import { BreadcrumbItem, BreadcrumbLink, @@ -11,6 +13,14 @@ export function HeaderFarmCreate({ b_name_farm: string | undefined | null }) { const location = useLocation() + const params = useParams() + const farmFieldOptionsStore = useFarmFieldOptionsStore() + useEffect(() => { + if (params.b_id_farm && b_name_farm) { + farmFieldOptionsStore.addFarmOption(params.b_id_farm, b_name_farm) + } + }, [params.b_id_farm, b_name_farm, farmFieldOptionsStore.addFarmOption]) + const currentPath = String(location.pathname) return ( diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx index 3cdf98ce2..0e00483e0 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx @@ -33,9 +33,12 @@ import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { extractFormValuesFromRequest } from "~/lib/form" import { AccessFormSchema } from "~/lib/schemas/access.schema" -import { SidebarInset } from "../components/ui/sidebar" -import { Header } from "../components/blocks/header/base" -import { HeaderFarmCreate } from "../components/blocks/header/create-farm" +import { Route } from "../+types/root" +import { Header } from "~/components/blocks/header/base" +import { HeaderFarmCreate } from "~/components/blocks/header/create-farm" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { SidebarInset } from "~/components/ui/sidebar" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -229,3 +232,36 @@ export async function action({ request, params }: ActionFunctionArgs) { // throw handleActionError(error) } } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const cachedFarmName = farmFieldOptionsStore.getFarmById( + params.b_id_farm, + )?.b_name_farm + + return ( + <> +
+ +
+
+
+
+
+

+ Toegang instellen (Optioneel) +

+

+ Nodig nu alvast gebruikers of organisaties uit, + of voltooi de wizard. +

+
+
+ +
+ +
+ + ) +} diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx index 047f6e469..e6ad97c30 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx @@ -15,6 +15,9 @@ import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" +import type { Route } from "../+types/root" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { useFarmFieldOptionsStore } from "../store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -133,3 +136,32 @@ export default function Index() { ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const cachedFarmName = farmFieldOptionsStore.getFarmById( + params.b_id_farm, + )?.b_name_farm + + return ( + <> +
+ +
+
+ + +
+ + ) +} diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx index ab7c172d4..b008585e6 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx @@ -15,6 +15,9 @@ import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" +import type { Route } from "../+types/root" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" // Meta export const meta: MetaFunction = () => { @@ -112,3 +115,32 @@ export default function Index() { ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const cachedFarmName = farmFieldOptionsStore.getFarmById( + params.b_id_farm, + )?.b_name_farm + + return ( + <> +
+ +
+
+ + +
+ + ) +} diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx index ee1b63716..5a86bb13a 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id._index.tsx @@ -58,7 +58,9 @@ import { clientConfig } from "~/lib/config" import { handleActionError, handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { extractFormValuesFromRequest } from "~/lib/form" -import { FieldDeleteDialog } from "../components/blocks/field/delete" +import type { Route } from "../+types/root" +import { FieldDeleteDialog } from "~/components/blocks/field/delete" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" // Meta export const meta: MetaFunction = () => { @@ -524,3 +526,11 @@ export async function action({ request, params }: ActionFunctionArgs) { return handleActionError(error) } } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + return ( +
+ +
+ ) +} diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx index afc7baf9d..d8c9ff985 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.$analysis_type.tsx @@ -5,6 +5,7 @@ import { data, type LoaderFunctionArgs, NavLink, + redirect, useLoaderData, } from "react-router" import { redirectWithSuccess } from "remix-toast" @@ -14,7 +15,7 @@ import { getSoilParametersForSoilAnalysisType } from "~/components/blocks/soil/p import { Button } from "~/components/ui/button" import { Separator } from "~/components/ui/separator" import { getSession } from "~/lib/auth.server" -import { handleActionError, handleLoaderError } from "~/lib/error" +import { handleActionError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { extractFormValuesFromRequest } from "~/lib/form" @@ -94,7 +95,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) { soilParameterDescription: soilAnalysisParameterDescription, } } catch (error) { - throw handleLoaderError(error) + const response = await handleActionError(error) + if (response.init) { + return redirect( + `/farm/create/${params.b_id_farm}/${params.calendar}/fields/${params.b_id}/soil/analysis`, + response.init, + ) + } + return response } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis._index.tsx index 372966dc6..436480477 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis._index.tsx @@ -1,11 +1,11 @@ import { getField } from "@svenvw/fdm-core" import { ArrowLeft } from "lucide-react" -import { data, type LoaderFunctionArgs, NavLink } from "react-router" +import { data, type LoaderFunctionArgs, NavLink, redirect } from "react-router" import { SoilAnalysisFormSelection } from "~/components/blocks/soil/form-selection" import { Button } from "~/components/ui/button" import { Separator } from "~/components/ui/separator" import { getSession } from "~/lib/auth.server" -import { handleLoaderError } from "~/lib/error" +import { handleActionError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" /** @@ -55,7 +55,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) { field: field, } } catch (error) { - throw handleLoaderError(error) + const response = await handleActionError(error) + if (response.init) { + return redirect( + `/farm/create/${params.b_id_farm}/${params.calendar}/fields/${params.b_id}`, + response.init, + ) + } + return response } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx index 1acad95e0..da773a162 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.$b_id.soil.analysis.upload.tsx @@ -10,6 +10,7 @@ import { type ActionFunctionArgs, data, type LoaderFunctionArgs, + redirect, } from "react-router" import { dataWithError, redirectWithSuccess } from "remix-toast" import { @@ -18,7 +19,7 @@ import { } from "~/components/blocks/soil/form-upload" import { extractSoilAnalysis } from "~/integrations/nmi" import { getSession } from "~/lib/auth.server" -import { handleActionError, handleLoaderError } from "~/lib/error" +import { handleActionError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" /** @@ -71,7 +72,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) { soilParameterDescription: soilParameterDescription, } } catch (error) { - throw handleLoaderError(error) + const response = await handleActionError(error) + if (response.init) { + return redirect( + `/farm/create/${params.b_id_farm}/${params.calendar}/fields/${params.b_id}/soil/analysis`, + response.init, + ) + } + return response } } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx index 1cbce7a1a..f506bc55b 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx @@ -20,6 +20,9 @@ import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { cn } from "~/lib/utils" +import type { Route } from "../+types/root" +import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -170,3 +173,36 @@ export default function Index() { ) } + +export function ErrorBoundary(props: Route.ErrorBoundaryProps) { + const { params } = props + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const cachedFarmName = farmFieldOptionsStore.getFarmById( + params.b_id_farm, + )?.b_name_farm + + return ( + +
+ +
+
+
+
+
+

+ Percelen +

+

+ Pas de naam aan, controleer het gewas en + bodemgegevens +

+
+
+ +
+ +
+
+ ) +} diff --git a/fdm-app/app/store/farm-field-options.tsx b/fdm-app/app/store/farm-field-options.tsx index 942383811..3153d6981 100644 --- a/fdm-app/app/store/farm-field-options.tsx +++ b/fdm-app/app/store/farm-field-options.tsx @@ -26,6 +26,13 @@ interface FarmFieldOptionsStore { * @param fieldOptions the list of HeaderFieldOption */ setFieldOptions(fieldOptions: HeaderFieldOption[]): void + /** + * Adds a singe farm option so it is later found by getFarmById + * + * @param b_id_farm The farm id + * @param b_name_farm The farm name + */ + addFarmOption(b_id_farm: string, b_name_farm: string): void /** * Tries to get the cached farm with the given id, otherwise returns undefined. * @@ -52,11 +59,20 @@ export const useFarmFieldOptionsStore = create( setFieldOptions(fieldOptions) { set({ fieldOptions }) }, + addFarmOption(b_id_farm, b_name_farm) { + console.log("adding farm option") + const item = { b_id_farm, b_name_farm } + set({ farmOptions: [item, ...get().farmOptions] }) + }, getFarmById(b_id_farm) { - return get().farmOptions.find((f) => f.b_id_farm === b_id_farm) + if (b_id_farm) { + return get().farmOptions.find((f) => f.b_id_farm === b_id_farm) + } }, getFieldById(b_id) { - return get().fieldOptions.find((f) => f.b_id === b_id) + if (b_id) { + return get().fieldOptions.find((f) => f.b_id === b_id) + } }, }), ) From ddb99a862ed29c5ecb0b3e412a4fa663a4bf63c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 21 Aug 2025 14:45:33 +0200 Subject: [PATCH 09/14] Rename farm-field-options.tsx to farm-field-options.ts --- .../app/store/{farm-field-options.tsx => farm-field-options.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename fdm-app/app/store/{farm-field-options.tsx => farm-field-options.ts} (100%) diff --git a/fdm-app/app/store/farm-field-options.tsx b/fdm-app/app/store/farm-field-options.ts similarity index 100% rename from fdm-app/app/store/farm-field-options.tsx rename to fdm-app/app/store/farm-field-options.ts From bd2725a7f6fab9ef61396215256463e09e75105e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 21 Aug 2025 15:26:09 +0200 Subject: [PATCH 10/14] Nitpicks --- .../components/blocks/header/create-farm.tsx | 8 +++++--- fdm-app/app/components/blocks/header/farm.tsx | 8 +++++--- .../app/components/blocks/header/field.tsx | 8 +++++--- ...eate.$b_id_farm.$calendar.cultivations.tsx | 2 +- fdm-app/app/store/farm-field-options.ts | 19 ++++++++++++++++--- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/fdm-app/app/components/blocks/header/create-farm.tsx b/fdm-app/app/components/blocks/header/create-farm.tsx index 995a151ba..09fbbf45d 100644 --- a/fdm-app/app/components/blocks/header/create-farm.tsx +++ b/fdm-app/app/components/blocks/header/create-farm.tsx @@ -14,12 +14,14 @@ export function HeaderFarmCreate({ }) { const location = useLocation() const params = useParams() - const farmFieldOptionsStore = useFarmFieldOptionsStore() + const addFarmOptionToTheStore = useFarmFieldOptionsStore( + (s) => s.addFarmOption, + ) useEffect(() => { if (params.b_id_farm && b_name_farm) { - farmFieldOptionsStore.addFarmOption(params.b_id_farm, b_name_farm) + addFarmOptionToTheStore(params.b_id_farm, b_name_farm) } - }, [params.b_id_farm, b_name_farm, farmFieldOptionsStore.addFarmOption]) + }, [params.b_id_farm, b_name_farm, addFarmOptionToTheStore]) const currentPath = String(location.pathname) diff --git a/fdm-app/app/components/blocks/header/farm.tsx b/fdm-app/app/components/blocks/header/farm.tsx index 6217931c3..681ff34e7 100644 --- a/fdm-app/app/components/blocks/header/farm.tsx +++ b/fdm-app/app/components/blocks/header/farm.tsx @@ -22,12 +22,14 @@ export function HeaderFarm({ farmOptions: HeaderFarmOption[] }) { const location = useLocation() - const farmFieldOptionsStore = useFarmFieldOptionsStore() + const setStoredFarmOptions = useFarmFieldOptionsStore( + (s) => s.setFarmOptions, + ) useEffect(() => { if (farmOptions && farmOptions.length > 0) { - farmFieldOptionsStore.setFarmOptions(farmOptions) + setStoredFarmOptions(farmOptions) } - }, [farmOptions, farmFieldOptionsStore.setFarmOptions]) + }, [farmOptions, setStoredFarmOptions]) const currentPath = String(location.pathname) diff --git a/fdm-app/app/components/blocks/header/field.tsx b/fdm-app/app/components/blocks/header/field.tsx index 8c91d6986..96a5f9fd7 100644 --- a/fdm-app/app/components/blocks/header/field.tsx +++ b/fdm-app/app/components/blocks/header/field.tsx @@ -27,12 +27,14 @@ export function HeaderField({ const location = useLocation() const currentPath = String(location.pathname) const calendar = useCalendarStore((state) => state.calendar) - const farmFieldOptionsStore = useFarmFieldOptionsStore() + const setStoredFieldOptions = useFarmFieldOptionsStore( + (s) => s.setFieldOptions, + ) useEffect(() => { if (fieldOptions && fieldOptions.length > 0) { - farmFieldOptionsStore.setFieldOptions(fieldOptions) + setStoredFieldOptions(fieldOptions) } - }, [fieldOptions, farmFieldOptionsStore.setFieldOptions]) + }, [fieldOptions, setStoredFieldOptions]) return ( <> diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx index e6ad97c30..0244212d7 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx @@ -17,7 +17,7 @@ import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import type { Route } from "../+types/root" import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" -import { useFarmFieldOptionsStore } from "../store/farm-field-options" +import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { diff --git a/fdm-app/app/store/farm-field-options.ts b/fdm-app/app/store/farm-field-options.ts index 3153d6981..427b9bbe9 100644 --- a/fdm-app/app/store/farm-field-options.ts +++ b/fdm-app/app/store/farm-field-options.ts @@ -4,6 +4,8 @@ import type { HeaderFieldOption } from "~/components/blocks/header/field" /** * Cache for farm and field options, to be used in error boundaries only. + * + * Do not use this store server-side */ interface FarmFieldOptionsStore { /** @@ -60,9 +62,20 @@ export const useFarmFieldOptionsStore = create( set({ fieldOptions }) }, addFarmOption(b_id_farm, b_name_farm) { - console.log("adding farm option") - const item = { b_id_farm, b_name_farm } - set({ farmOptions: [item, ...get().farmOptions] }) + set((state) => { + const item = { b_id_farm, b_name_farm } + + const duplicateIndex = state.farmOptions.findIndex( + (f) => f.b_id_farm === b_id_farm, + ) + if (duplicateIndex > -1) { + const newFarmOptions = state.farmOptions.slice() + newFarmOptions[duplicateIndex] = item + return { farmOptions: newFarmOptions } + } + + return { farmOptions: [item, ...get().farmOptions] } + }) }, getFarmById(b_id_farm) { if (b_id_farm) { From 5163c17267a8f315c9df1721676c54a5b1694ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 21 Aug 2025 15:30:01 +0200 Subject: [PATCH 11/14] Remove console.log --- fdm-app/app/routes/farm.$b_id_farm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/fdm-app/app/routes/farm.$b_id_farm.tsx b/fdm-app/app/routes/farm.$b_id_farm.tsx index f0bd9d7bd..397e570e6 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.tsx @@ -59,7 +59,6 @@ export function ErrorBoundary(props: Route.ErrorBoundaryProps) { const farmFieldOptionsStore = useFarmFieldOptionsStore() const { params } = props - console.log(params) return (
From ec6c5c0c8f879e08a5b311cde6ec8b2af7ab9a73 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:46:04 +0200 Subject: [PATCH 12/14] refactor: Improve the design of the error block to fit better in the page when shown --- .changeset/tiny-words-drum.md | 5 + fdm-app/app/components/custom/error.tsx | 165 ++++++++++++------------ fdm-app/app/tailwind.css | 13 ++ 3 files changed, 104 insertions(+), 79 deletions(-) create mode 100644 .changeset/tiny-words-drum.md diff --git a/.changeset/tiny-words-drum.md b/.changeset/tiny-words-drum.md new file mode 100644 index 000000000..2349062c0 --- /dev/null +++ b/.changeset/tiny-words-drum.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Improve the design of the error block to fit better in the page when shown diff --git a/fdm-app/app/components/custom/error.tsx b/fdm-app/app/components/custom/error.tsx index 916aa2568..e489a708c 100644 --- a/fdm-app/app/components/custom/error.tsx +++ b/fdm-app/app/components/custom/error.tsx @@ -1,22 +1,21 @@ -import { ArrowLeft, Copy, Home } from "lucide-react" +import { Copy, Home, RefreshCw } from "lucide-react" import { useEffect, useState } from "react" import { NavLink } from "react-router" import { Button } from "~/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card" +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "~/components/ui/accordion" +import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert" /** - * Displays a full-screen error block with tailored messaging and navigation options. - * - * Depending on the provided error status, this component renders: - * - A specific message and navigation buttons for a 404 error, indicating that the page does not exist. - * - A generic error message along with a button to copy the formatted error details (including status, message, stack trace, page, and timestamp) to the clipboard for other errors. - * - * If an error message is available, the component also displays the error details formatted as pretty-printed JSON. Otherwise, it shows a fallback message for non-404 errors. + * Displays an error block with tailored messaging and navigation options. + * It can be used as a full-screen error or within a component. * * @param status - HTTP status code of the error or null. * @param message - Detailed error message, or null if not available. * @param stacktrace - Optional stack trace providing additional error context. * @param page - The page where the error occurred. * @param timestamp - The timestamp when the error was recorded. + * @param actions - Optional array of action buttons to display. Each action should have a `label`, `onClick` function, and an optional `icon`. */ export function ErrorBlock({ status, @@ -24,12 +23,14 @@ export function ErrorBlock({ stacktrace, page, timestamp, + actions, }: { status: number | null message: string | null stacktrace: string | null | undefined page: string timestamp: string + actions?: { label: string; onClick: () => void; icon?: React.ReactNode; variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" }[] }) { const [isCopied, setIsCopied] = useState(false) @@ -51,80 +52,86 @@ export function ErrorBlock({ null, 2, ) - const copyStackTrace = () => { - navigator.clipboard.writeText(errorDetails) - setIsCopied(true) + + const isNetworkError = status === 502 || status === 503 || status === 504 || !status; + + const defaultActions = []; + + if (isNetworkError) { + defaultActions.push({ + label: "Pagina herladen", + onClick: () => window.location.reload(), + icon: , + variant: "default" as const, + }); } + + defaultActions.push( + { + label: "Terug naar de hoofdpagina", + onClick: () => { + window.location.href = "/" + }, + icon: , + variant: "outline" as const, + }, + { + label: isCopied ? "Gekopieerd!" : "Kopieer foutmelding", + onClick: () => { + navigator.clipboard.writeText(errorDetails) + setIsCopied(true) + }, + icon: , + variant: "ghost" as const, + }, + ); + + const currentActions = actions || defaultActions; + return ( -
- {status === 404 && ( -
- 404 -
- )} - {status !== 404 && ( -
- A red tractor doing a wheelie -
- )} -

- {status === 404 - ? "Aii, deze pagina bestaat niet." - : "Oeps, er lijkt iets mis te zijn."} -

-

- {status === 404 - ? "Het lijkt erop dat de pagina die je zoekt niet bestaat." - : "Er is onverwachts wat fout gegaan. Probeer eerst opnieuw. Als het niet opnieuw lukt, kopieer dan de foutmelding en neem contact op met Ondersteuning."} -

+
+ {/* Added overflow-hidden to Card */} + +
{/* Removed overflow-hidden from inner div */} +
+ 🚜 +
+
+ + {status === 404 + ? "Oeps, deze pagina bestaat niet." + : "Er ging iets mis."} + + + {status === 404 + ? "Het lijkt erop dat de pagina die je zoekt niet bestaat. Geen zorgen, we helpen je graag verder!" + : "Er is een onverwachte fout opgetreden. Probeer het opnieuw of neem contact op met ondersteuning als het probleem aanhoudt."} + +
+ +
+ {currentActions.map((action, index) => ( + + ))} +
- {status === 404 ? ( -
- - -
- ) : ( -
- - -
- )} - {message ? ( -
-

- Foutmelding: -

-
-                        {errorDetails}
-                    
-
- ) : status === 404 ? null : ( -

- Er zijn helaas geen details over de fout beschikbaar. -

- )} + {message && ( + + + Foutmelding details + +
+                                        {errorDetails}
+                                    
+
+
+
+ )} +
+
) } diff --git a/fdm-app/app/tailwind.css b/fdm-app/app/tailwind.css index 870a9f029..83b4087ca 100644 --- a/fdm-app/app/tailwind.css +++ b/fdm-app/app/tailwind.css @@ -171,3 +171,16 @@ /* text-transform: uppercase; */ /* outline: none; */ } + +@keyframes tractor-drive { + 100% { + transform: translateX(calc(-100% - 5rem)); + } + 0% { + transform: translateX(calc(100% + 20rem)); /* Adjusted to move beyond the card width */ + } +} + +.animate-tractor-drive { + animation: tractor-drive 5s linear infinite; +} From 264ea16fd9e447df18cc5407d9536e2e5479aea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 22 Aug 2025 16:55:09 +0200 Subject: [PATCH 13/14] Add automatic header - next phase will be to remove some of the repetitive error boundaries --- .../components/blocks/header/automatic.tsx | 188 ++++++++++++++++++ fdm-app/app/components/blocks/header/farm.tsx | 10 +- .../app/components/blocks/header/field.tsx | 10 +- fdm-app/app/routes/farm.$b_id_farm.tsx | 43 ++++ fdm-app/app/routes/farm.tsx | 2 + 5 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 fdm-app/app/components/blocks/header/automatic.tsx diff --git a/fdm-app/app/components/blocks/header/automatic.tsx b/fdm-app/app/components/blocks/header/automatic.tsx new file mode 100644 index 000000000..a087a3491 --- /dev/null +++ b/fdm-app/app/components/blocks/header/automatic.tsx @@ -0,0 +1,188 @@ +import { ReactNode } from "react" +import { useLocation, useMatches, useParams } from "react-router" +import { useCalendarStore } from "@/app/store/calendar" +import { useFarmFieldOptionsStore } from "@/app/store/farm-field-options" +import { HeaderAtlas } from "./atlas" +import { HeaderBalance } from "./balance" +import { Header } from "./base" +import { HeaderFarmCreate } from "./create-farm" +import { HeaderFarm, type HeaderFarmOption } from "./farm" +import { HeaderFertilizer } from "./fertilizer" +import { HeaderField, type HeaderFieldOption } from "./field" +import { HeaderNorms } from "./norms" +import { HeaderNutrientAdvice } from "./nutrient-advice" + +export default function HeaderAutomatic() { + const matches = useMatches() + const params = useParams() + const location = useLocation() + const farmFieldOptionsStore = useFarmFieldOptionsStore() + const storedCalendar = useCalendarStore((s) => s.calendar) + + // Find the farm and field options. + let farmOptions: HeaderFarmOption[] | undefined + let fieldOptions: HeaderFieldOption[] | undefined + let fertilizerOptions: unknown[] | undefined + let b_name_farm: string | undefined + + for (const match of matches) { + if (match.loaderData) { + farmOptions ??= ( + match.loaderData as { farmOptions?: HeaderFarmOption[] } + ).farmOptions + fieldOptions ??= ( + match.loaderData as { fieldOptions?: HeaderFieldOption[] } + ).fieldOptions + fertilizerOptions ??= ( + match.loaderData as { fertilizerOptions?: unknown[] } + ).fertilizerOptions + } + } + + farmOptions ??= farmFieldOptionsStore.farmOptions + fieldOptions ??= farmFieldOptionsStore.fieldOptions + b_name_farm ??= farmFieldOptionsStore.getFarmById( + params.b_id_farm, + )?.b_name_farm + const calendar = params.calendar ?? storedCalendar + + if (/create/.test(location.pathname)) { + return ( +
+ +
+ ) + } + + const variants: Record ReactNode> = { + "routes/farm._index": () => ( +
+ +
+ ), + "routes/farm.$b_id_farm.settings.properties": () => ( +
+ +
+ ), + "routes/farm.$b_id_farm.$calendar.field.new": () => + variants["routes/farm.$b_id_farm.$calendar.field._index"](), + "routes/farm.$b_id_farm.$calendar.field.$b_id": () => + variants["routes/farm.$b_id_farm.$calendar.field._index"](), + "routes/farm.$b_id_farm.$calendar.field._index": () => ( +
+ + +
+ ), + "routes/farm.$b_id_farm.fertilizers.$p_id": () => + variants["routes/farm.$b_id_farm.fertilizers._index"](), + "routes/farm.$b_id_farm.fertilizers._index": () => ( +
+ + +
+ ), + "routes/farm.$b_id_farm.$calendar.atlas": () => { + const isFieldDetailsPage = + location.pathname.includes("/atlas/fields/") && + location.pathname.split("/atlas/fields/")[1]?.includes(",") + let headerAction: + | { to: string; label: string; disabled: boolean } + | undefined + if (isFieldDetailsPage) { + headerAction = { + to: `/farm/${params.b_id_farm}/${calendar}/atlas/fields`, + label: "Terug", + disabled: false, + } + } + return ( +
+ + +
+ ) + }, + "routes/farm.$b_id_farm.$calendar.balance.nitrogen": () => ( +
+ + +
+ ), + "routes/farm.$b_id_farm.$calendar.nutrient_advice": () => ( +
+ + +
+ ), + "routes/farm.$b_id_farm.$calendar.norms": () => ( +
+ + +
+ ), + } + + let chosenVariant: (() => ReactNode) | undefined + + for (const match of matches) { + if (match.id in variants) { + chosenVariant = variants[match.id] + break + } + } + + return chosenVariant ? chosenVariant() : null +} diff --git a/fdm-app/app/components/blocks/header/farm.tsx b/fdm-app/app/components/blocks/header/farm.tsx index 681ff34e7..7f7ce3150 100644 --- a/fdm-app/app/components/blocks/header/farm.tsx +++ b/fdm-app/app/components/blocks/header/farm.tsx @@ -25,11 +25,11 @@ export function HeaderFarm({ const setStoredFarmOptions = useFarmFieldOptionsStore( (s) => s.setFarmOptions, ) - useEffect(() => { - if (farmOptions && farmOptions.length > 0) { - setStoredFarmOptions(farmOptions) - } - }, [farmOptions, setStoredFarmOptions]) + // useEffect(() => { + // if (farmOptions && farmOptions.length > 0) { + // setStoredFarmOptions(farmOptions) + // } + // }, [farmOptions, setStoredFarmOptions]) const currentPath = String(location.pathname) diff --git a/fdm-app/app/components/blocks/header/field.tsx b/fdm-app/app/components/blocks/header/field.tsx index 96a5f9fd7..4a4a94b8c 100644 --- a/fdm-app/app/components/blocks/header/field.tsx +++ b/fdm-app/app/components/blocks/header/field.tsx @@ -30,11 +30,11 @@ export function HeaderField({ const setStoredFieldOptions = useFarmFieldOptionsStore( (s) => s.setFieldOptions, ) - useEffect(() => { - if (fieldOptions && fieldOptions.length > 0) { - setStoredFieldOptions(fieldOptions) - } - }, [fieldOptions, setStoredFieldOptions]) + // useEffect(() => { + // if (fieldOptions && fieldOptions.length > 0) { + // setStoredFieldOptions(fieldOptions) + // } + // }, [fieldOptions, setStoredFieldOptions]) return ( <> diff --git a/fdm-app/app/routes/farm.$b_id_farm.tsx b/fdm-app/app/routes/farm.$b_id_farm.tsx index 397e570e6..0af8769fd 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.tsx @@ -1,3 +1,4 @@ +import { getFarms, getFields } from "@svenvw/fdm-core" import { data, type LoaderFunctionArgs, type MetaFunction } from "react-router" import { Header } from "~/components/blocks/header/base" import { HeaderFarm } from "~/components/blocks/header/farm" @@ -6,8 +7,11 @@ import { SidebarInset } from "~/components/ui/sidebar" import { getSession } from "~/lib/auth.server" import { clientConfig } from "~/lib/config" import { handleActionError } from "~/lib/error" +import { fdm } from "~/lib/fdm.server" import { useFarmFieldOptionsStore } from "~/store/farm-field-options" import type { Route } from "../+types/root" +import type { HeaderFieldOption } from "~/components/blocks/header/field" +import { getTimeframe } from "../lib/calendar" // Meta export const meta: MetaFunction = () => { @@ -45,9 +49,48 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Get the session const session = await getSession(request) + // Get a list of possible farms of the user + const farms = await getFarms(fdm, session.principal_id) + if (!farms || farms.length === 0) { + throw data("not found: farms", { + status: 404, + statusText: "not found: farms", + }) + } + const farmOptions = farms.map((farm) => { + return { + b_id_farm: farm.b_id_farm, + b_name_farm: farm.b_name_farm, + } + }) + + // Get the fields to be selected if available + const timeframe = getTimeframe(params) + let fieldOptions: HeaderFieldOption[] = [] + if (timeframe) { + const fields = await getFields( + fdm, + session.principal_id, + b_id_farm, + timeframe, + ) + fieldOptions = fields.map((field) => { + if (!field?.b_id || !field?.b_name) { + throw new Error("Invalid field data structure") + } + return { + b_id: field.b_id, + b_name: field.b_name, + b_area: Math.round(field.b_area * 10) / 10, + } + }) + } + // Return the farm ID and session info return { farmId: b_id_farm, + farmOptions, + fieldOptions, session, } } catch (error) { diff --git a/fdm-app/app/routes/farm.tsx b/fdm-app/app/routes/farm.tsx index a91909683..c6139880f 100644 --- a/fdm-app/app/routes/farm.tsx +++ b/fdm-app/app/routes/farm.tsx @@ -19,6 +19,7 @@ import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { useCalendarStore } from "~/store/calendar" import { useFarmStore } from "~/store/farm" +import HeaderAutomatic from "~/components/blocks/header/automatic" export const meta: MetaFunction = () => { return [ @@ -132,6 +133,7 @@ export default function App() { /> + From 7a08934b135455e8aa400cba3a957f86bee5ec88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Fri, 5 Sep 2025 17:25:46 +0200 Subject: [PATCH 14/14] Remove repeated loading of the same data and add generic header to the overall farm page --- .../components/blocks/header/automatic.tsx | 52 +++++---- fdm-app/app/components/blocks/header/farm.tsx | 9 -- .../components/blocks/header/fertilizer.tsx | 22 +--- .../app/components/blocks/header/field.tsx | 8 -- .../farm.$b_id_farm.$calendar.atlas.tsx | 10 -- ....$b_id_farm.$calendar.balance.nitrogen.tsx | 81 +------------- .../farm.$b_id_farm.$calendar.field.$b_id.tsx | 61 ----------- ...farm.$b_id_farm.$calendar.field._index.tsx | 32 ------ .../farm.$b_id_farm.$calendar.field.new.tsx | 36 ------ .../farm.$b_id_farm.$calendar.norms.tsx | 42 ------- ...m.$b_id_farm.$calendar.nutrient_advice.tsx | 50 +-------- .../farm.$b_id_farm.fertilizers.$p_id.tsx | 56 ---------- .../farm.$b_id_farm.fertilizers._index.tsx | 44 +------- .../farm.$b_id_farm.fertilizers.new.$p_id.tsx | 33 ------ ...farm.$b_id_farm.fertilizers.new.custom.tsx | 15 --- .../farm.$b_id_farm.fertilizers.new.tsx | 37 ------- .../routes/farm.$b_id_farm.fertilizers.tsx | 65 ++++++----- .../app/routes/farm.$b_id_farm.settings.tsx | 38 +------ fdm-app/app/routes/farm.$b_id_farm.tsx | 76 +------------ fdm-app/app/routes/farm._index.tsx | 27 +---- ...arm.create.$b_id_farm.$calendar._index.tsx | 22 +--- ...arm.create.$b_id_farm.$calendar.access.tsx | 19 +--- ...farm.create.$b_id_farm.$calendar.atlas.tsx | 6 +- ...eate.$b_id_farm.$calendar.cultivations.tsx | 89 ++++++--------- ....$calendar.fertilizers.$b_lu_catalogue.tsx | 17 --- ...reate.$b_id_farm.$calendar.fertilizers.tsx | 103 ++++++++---------- ...arm.create.$b_id_farm.$calendar.fields.tsx | 15 --- ...arm.create.$b_id_farm.$calendar.upload.tsx | 9 +- fdm-app/app/routes/farm.create._index.tsx | 5 - fdm-app/app/routes/farm.tsx | 22 +++- 30 files changed, 189 insertions(+), 912 deletions(-) diff --git a/fdm-app/app/components/blocks/header/automatic.tsx b/fdm-app/app/components/blocks/header/automatic.tsx index a087a3491..a5897ff09 100644 --- a/fdm-app/app/components/blocks/header/automatic.tsx +++ b/fdm-app/app/components/blocks/header/automatic.tsx @@ -1,7 +1,8 @@ -import { ReactNode } from "react" -import { useLocation, useMatches, useParams } from "react-router" +import type { ReactNode } from "react" +import { type UIMatch, useLocation, useMatches, useParams } from "react-router" import { useCalendarStore } from "@/app/store/calendar" import { useFarmFieldOptionsStore } from "@/app/store/farm-field-options" +import type { FertilizerOption } from "../farm/farm" import { HeaderAtlas } from "./atlas" import { HeaderBalance } from "./balance" import { Header } from "./base" @@ -13,7 +14,13 @@ import { HeaderNorms } from "./norms" import { HeaderNutrientAdvice } from "./nutrient-advice" export default function HeaderAutomatic() { - const matches = useMatches() + interface LoaderDataCandidate { + b_name_farm?: string + farmOptions?: HeaderFarmOption[] + fieldOptions?: HeaderFieldOption[] + fertilizerOptions?: FertilizerOption[] + } + const matches = useMatches() as UIMatch[] const params = useParams() const location = useLocation() const farmFieldOptionsStore = useFarmFieldOptionsStore() @@ -27,26 +34,22 @@ export default function HeaderAutomatic() { for (const match of matches) { if (match.loaderData) { - farmOptions ??= ( - match.loaderData as { farmOptions?: HeaderFarmOption[] } - ).farmOptions - fieldOptions ??= ( - match.loaderData as { fieldOptions?: HeaderFieldOption[] } - ).fieldOptions - fertilizerOptions ??= ( - match.loaderData as { fertilizerOptions?: unknown[] } - ).fertilizerOptions + b_name_farm ??= match.loaderData.b_name_farm + farmOptions ??= match.loaderData.farmOptions + fieldOptions ??= match.loaderData.fieldOptions + fertilizerOptions ??= match.loaderData.fertilizerOptions } } farmOptions ??= farmFieldOptionsStore.farmOptions fieldOptions ??= farmFieldOptionsStore.fieldOptions - b_name_farm ??= farmFieldOptionsStore.getFarmById( - params.b_id_farm, - )?.b_name_farm + b_name_farm ??= + farmFieldOptionsStore.getFarmById(params.b_id_farm)?.b_name_farm ?? + undefined + const calendar = params.calendar ?? storedCalendar - if (/create/.test(location.pathname)) { + if (/\/create\//.test(location.pathname)) { return (
@@ -57,10 +60,13 @@ export default function HeaderAutomatic() { const variants: Record ReactNode> = { "routes/farm._index": () => (
- +
), - "routes/farm.$b_id_farm.settings.properties": () => ( + "routes/farm.$b_id_farm.settings": () => (
), - "routes/farm.$b_id_farm.fertilizers.$p_id": () => - variants["routes/farm.$b_id_farm.fertilizers._index"](), - "routes/farm.$b_id_farm.fertilizers._index": () => ( + "routes/farm.$b_id_farm.fertilizers": () => (
), diff --git a/fdm-app/app/components/blocks/header/farm.tsx b/fdm-app/app/components/blocks/header/farm.tsx index 7f7ce3150..c90753c22 100644 --- a/fdm-app/app/components/blocks/header/farm.tsx +++ b/fdm-app/app/components/blocks/header/farm.tsx @@ -1,5 +1,4 @@ import { ChevronDown } from "lucide-react" -import { useEffect } from "react" import { NavLink, useLocation } from "react-router" import { useFarmFieldOptionsStore } from "@/app/store/farm-field-options" import { @@ -22,14 +21,6 @@ export function HeaderFarm({ farmOptions: HeaderFarmOption[] }) { const location = useLocation() - const setStoredFarmOptions = useFarmFieldOptionsStore( - (s) => s.setFarmOptions, - ) - // useEffect(() => { - // if (farmOptions && farmOptions.length > 0) { - // setStoredFarmOptions(farmOptions) - // } - // }, [farmOptions, setStoredFarmOptions]) const currentPath = String(location.pathname) diff --git a/fdm-app/app/components/blocks/header/fertilizer.tsx b/fdm-app/app/components/blocks/header/fertilizer.tsx index 98577ce94..a9853eca2 100644 --- a/fdm-app/app/components/blocks/header/fertilizer.tsx +++ b/fdm-app/app/components/blocks/header/fertilizer.tsx @@ -1,5 +1,4 @@ import { ChevronDown } from "lucide-react" -import { useEffect } from "react" import { NavLink, useLocation } from "react-router" import { create } from "zustand" import { @@ -27,30 +26,13 @@ const useFertilizerOptionsStore = create<{ export function HeaderFertilizer({ b_id_farm, p_id, - fertilizerOptions: pFertilizerOptions, + fertilizerOptions, }: { b_id_farm: string p_id: string | undefined fertilizerOptions: HeaderFertilizerOption[] | undefined }) { const location = useLocation() - const fertilizerOptionsStore = useFertilizerOptionsStore() - - useEffect(() => { - if ( - pFertilizerOptions && - pFertilizerOptions !== fertilizerOptionsStore.fertilizerOptions - ) { - fertilizerOptionsStore.setFertilizerOptions(pFertilizerOptions) - } - }, [ - pFertilizerOptions, - fertilizerOptionsStore.fertilizerOptions, - fertilizerOptionsStore.setFertilizerOptions, - ]) - - const fertilizerOptions = - pFertilizerOptions ?? fertilizerOptionsStore.fertilizerOptions const currentPath = String(location.pathname) @@ -58,7 +40,7 @@ export function HeaderFertilizer({ <> - + Meststof diff --git a/fdm-app/app/components/blocks/header/field.tsx b/fdm-app/app/components/blocks/header/field.tsx index 4a4a94b8c..86476564c 100644 --- a/fdm-app/app/components/blocks/header/field.tsx +++ b/fdm-app/app/components/blocks/header/field.tsx @@ -27,14 +27,6 @@ export function HeaderField({ const location = useLocation() const currentPath = String(location.pathname) const calendar = useCalendarStore((state) => state.calendar) - const setStoredFieldOptions = useFarmFieldOptionsStore( - (s) => s.setFieldOptions, - ) - // useEffect(() => { - // if (fieldOptions && fieldOptions.length > 0) { - // setStoredFieldOptions(fieldOptions) - // } - // }, [fieldOptions, setStoredFieldOptions]) return ( <> diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.atlas.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.atlas.tsx index 225a0cb62..e3bf70553 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.atlas.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.atlas.tsx @@ -8,9 +8,6 @@ import { useLocation, } from "react-router" import { ClientOnly } from "remix-utils/client-only" -import { HeaderAtlas } from "~/components/blocks/header/atlas" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" import { SidebarInset } from "~/components/ui/sidebar" import { Skeleton } from "~/components/ui/skeleton" import { getSession } from "~/lib/auth.server" @@ -111,13 +108,6 @@ export default function FarmContentBlock() { return ( -
- - -
} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx index e28fd139a..42381e560 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.tsx @@ -1,24 +1,17 @@ -import { getFarm, getFarms, getFields } from "@svenvw/fdm-core" +import { getFields } from "@svenvw/fdm-core" import { data, type LoaderFunctionArgs, type MetaFunction, Outlet, - useLoaderData, } from "react-router" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { HeaderBalance } from "~/components/blocks/header/balance" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" import { SidebarInset } from "~/components/ui/sidebar" +import { clientConfig } from "~/lib/config" import { getSession } from "~/lib/auth.server" import { getTimeframe } from "~/lib/calendar" -import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" -import type { Route } from "../+types/root" -import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" -import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -56,40 +49,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }) } - // Get the field id - const b_id = params.b_id - // Get the session const session = await getSession(request) // Get timeframe from calendar store const timeframe = getTimeframe(params) - // Get details of farm - const farm = await getFarm(fdm, session.principal_id, b_id_farm) - if (!farm) { - throw data("not found: b_id_farm", { - status: 404, - statusText: "not found: b_id_farm", - }) - } - - // Get a list of possible farms of the user - const farms = await getFarms(fdm, session.principal_id) - if (!farms || farms.length === 0) { - throw data("not found: farms", { - status: 404, - statusText: "not found: farms", - }) - } - - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get the fields to be selected const fields = await getFields( fdm, @@ -110,10 +75,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { - farm: farm, - b_id_farm: b_id_farm, - b_id: b_id, - farmOptions: farmOptions, fieldOptions: fieldOptions, } } catch (error) { @@ -128,21 +89,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { * It also renders a main section containing the farm title, description, nested routes via an Outlet, and a notification toaster. */ export default function FarmBalanceNitrogenBlock() { - const loaderData = useLoaderData() - return ( -
- - -
) } - -export function ErrorBoundary(props: Route.ErrorBoundaryProps) { - const { params } = props - - const farmFieldOptionsStore = useFarmFieldOptionsStore() - - return ( - -
- - -
-
- -
-
- ) -} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx index f4417d422..f1015619d 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.tsx @@ -4,14 +4,10 @@ import { type LoaderFunctionArgs, type MetaFunction, Outlet, - redirect, useLoaderData, } from "react-router" import { FarmContent } from "~/components/blocks/farm/farm-content" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" -import { HeaderField } from "~/components/blocks/header/field" import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" import { SidebarInset } from "~/components/ui/sidebar" import { getSession } from "~/lib/auth.server" @@ -19,7 +15,6 @@ import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" -import { useCalendarStore } from "~/store/calendar" import { useFarmFieldOptionsStore } from "~/store/farm-field-options" import type { Route } from "../+types/root" @@ -122,25 +117,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const calendar = getCalendar(params) const timeframe = getTimeframe(params) - // Get a list of possible farms of the user - const farms = await getFarms(fdm, session.principal_id) - - // Redirect to farms overview if user has no farm - if (farms.length === 0) { - return redirect("farm") - } - - // Get farms to be selected - const farmOptions = farms.map((farm) => { - if (!farm?.b_id_farm || !farm?.b_name_farm) { - throw new Error("Invalid farm data structure") - } - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get the fields to be selected const fields = await getFields( fdm, @@ -177,7 +153,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { b_id_farm: b_id_farm, - farmOptions: farmOptions, fieldOptions: fieldOptions, field: field, b_id: b_id, @@ -200,27 +175,9 @@ export async function loader({ request, params }: LoaderFunctionArgs) { */ export default function FarmFieldIndex() { const loaderData = useLoaderData() - const calendar = useCalendarStore((state) => state.calendar) return ( -
- - -
-
- - -
- {params.b_id_farm && params.calendar && params.b_id ? (
{ - if (!farm?.b_id_farm || !farm?.b_name_farm) { - throw new Error("Invalid farm data structure") - } - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get the fields to be selected const fields = await getFields( fdm, @@ -116,7 +102,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { b_id_farm: b_id_farm, - farmOptions: farmOptions, fieldOptions: fieldOptions, userName: session.userName, } @@ -142,23 +127,6 @@ export default function FarmFieldIndex() { return ( -
- - -
{loaderData.fieldOptions.length === 0 ? ( <> diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx index 8b501c183..62fab0c9a 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx @@ -4,7 +4,6 @@ import { addSoilAnalysis, getCultivationsFromCatalogue, getFarm, - getFarms, getFields, } from "@svenvw/fdm-core" import type { Feature, FeatureCollection, Polygon } from "geojson" @@ -37,9 +36,6 @@ import { getFieldsStyle } from "~/components/blocks/atlas/atlas-styles" import { getViewState } from "~/components/blocks/atlas/atlas-viewstate" import FieldDetailsDialog from "~/components/blocks/field/form" import { FormSchema } from "~/components/blocks/field/schema" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" -import { HeaderField } from "~/components/blocks/header/field" import { Separator } from "~/components/ui/separator" import { SidebarInset } from "~/components/ui/sidebar" import { Skeleton } from "~/components/ui/skeleton" @@ -51,7 +47,6 @@ import { clientConfig } from "~/lib/config" import { handleActionError, handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import { extractFormValuesFromRequest } from "~/lib/form" -import { useCalendarStore } from "~/store/calendar" // Meta export const meta: MetaFunction = () => { @@ -91,18 +86,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const calendar = getCalendar(params) const timeframe = getTimeframe(params) - // Get a list of possible farms of the user - const farms = await getFarms(fdm, session.principal_id) - const farmOptions = farms.map((farm) => { - if (!farm?.b_id_farm || !farm?.b_name_farm) { - throw new Error("Invalid farm data structure") - } - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - const farm = await getFarm(fdm, session.principal_id, b_id_farm) if (!farm) { @@ -170,7 +153,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const mapboxStyle = getMapboxStyle() return { - farmOptions: farmOptions, b_id_farm: b_id_farm, b_name_farm: farm.b_name_farm, calendar: calendar, @@ -188,7 +170,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Main export default function Index() { const loaderData = useLoaderData() - const calendar = useCalendarStore((state) => state.calendar) const fieldsSavedId = "fieldsSaved" const fieldsSaved = loaderData.featureCollection @@ -226,23 +207,6 @@ export default function Index() { return ( -
- - -
{/* { @@ -85,13 +79,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }) } - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get the fields to be selected const fields = await getFields( fdm, @@ -183,7 +170,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { b_id_farm: b_id_farm, b_id: b_id, calendar: calendar, - farmOptions: farmOptions, fieldOptions: fieldOptions, asyncData, } @@ -197,13 +183,6 @@ export default function FarmNormsBlock() { return ( -
- - -
>) {
) } - -export function ErrorBoundary(props: Route.ErrorBoundaryProps) { - const { params } = props - - const farmFieldOptionsStore = useFarmFieldOptionsStore() - - return ( - -
- - -
-
- -
-
- ) -} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.tsx index 54e8cf3fb..309b14893 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.nutrient_advice.tsx @@ -7,15 +7,12 @@ import { useLoaderData, } from "react-router" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" -import { HeaderNutrientAdvice } from "~/components/blocks/header/nutrient-advice" import { SidebarInset } from "~/components/ui/sidebar" import { getSession } from "~/lib/auth.server" -import { getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" +import { getTimeframe } from "~/lib/calendar" // Meta export const meta: MetaFunction = () => { @@ -53,40 +50,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }) } - // Get the field id - const b_id = params.b_id - // Get the session const session = await getSession(request) // Get timeframe from calendar store const timeframe = getTimeframe(params) - // Get details of farm - const farm = await getFarm(fdm, session.principal_id, b_id_farm) - if (!farm) { - throw data("not found: b_id_farm", { - status: 404, - statusText: "not found: b_id_farm", - }) - } - - // Get a list of possible farms of the user - const farms = await getFarms(fdm, session.principal_id) - if (!farms || farms.length === 0) { - throw data("not found: farms", { - status: 404, - statusText: "not found: farms", - }) - } - - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get the fields to be selected const fields = await getFields( fdm, @@ -107,10 +76,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { - farm: farm, - b_id_farm: b_id_farm, - b_id: b_id, - farmOptions: farmOptions, fieldOptions: fieldOptions, } } catch (error) { @@ -125,21 +90,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { * It also renders a main section containing the farm title, description, nested routes via an Outlet, and a notification toaster. */ export default function FarmBalanceNitrogenBlock() { - const loaderData = useLoaderData() - return ( -
- - -
{ - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get selected fertilizer const fertilizer = await getFertilizer(fdm, p_id) const fertilizerParameters = getFertilizerParametersDescription() - // Get the available fertilizers - const fertilizers = await getFertilizers( - fdm, - session.principal_id, - b_id_farm, - ) - const fertilizerOptions = fertilizers.map((fertilizer) => { - return { - p_id: fertilizer.p_id, - p_name_nl: fertilizer.p_name_nl || "", - } - }) - // Set editable status let editable = false if (fertilizer.p_source === b_id_farm) { @@ -113,11 +79,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { - farm: farm, - p_id: p_id, - b_id_farm: b_id_farm, - farmOptions: farmOptions, - fertilizerOptions: fertilizerOptions, fertilizer: fertilizer, editable: editable, fertilizerParameters: fertilizerParameters, @@ -191,23 +152,6 @@ export default function FarmFertilizerBlock() { return ( -
- - -
{ - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get the available fertilizers const fertilizers: Fertilizer[] = await getFertilizers( fdm, @@ -78,9 +50,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { - farm: farm, - b_id_farm: b_id_farm, - farmOptions: farmOptions, fertilizers: fertilizers, } } catch (error) { @@ -99,17 +68,6 @@ export default function FarmFertilizersBlock() { return ( -
- - -
{ - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Get selected fertilizer const fertilizer = await getFertilizer(fdm, p_id) const fertilizerParameters = getFertilizerParametersDescription() - // Get the available fertilizers - const fertilizers = await getFertilizers( - fdm, - session.principal_id, - b_id_farm, - ) - const fertilizerOptions = fertilizers.map((fertilizer) => { - return { - p_id: fertilizer.p_id, - p_name_nl: fertilizer.p_name_nl, - } - }) - // Return user information from loader return { farm: farm, p_id: p_id, b_id_farm: b_id_farm, - farmOptions: farmOptions, - fertilizerOptions: fertilizerOptions, fertilizer: fertilizer, editable: true, fertilizerParameters: fertilizerParameters, diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx index 2f22cf543..2a4fec72e 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx @@ -3,7 +3,6 @@ import { addFertilizer, addFertilizerToCatalogue, getFertilizerParametersDescription, - getFertilizers, } from "@svenvw/fdm-core" import { useEffect } from "react" import { @@ -102,22 +101,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { p_app_method_options: [], } - // Get the available fertilizers - const fertilizers = await getFertilizers( - fdm, - session.principal_id, - b_id_farm, - ) - const fertilizerOptions = fertilizers.map((fertilizer) => { - return { - p_id: fertilizer.p_id, - p_name_nl: fertilizer.p_name_nl, - } - }) - // Return user information from loader return { - fertilizerOptions: fertilizerOptions, fertilizer: fertilizer, fertilizerParameters: fertilizerParameters, } diff --git a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx index d603a39da..41c9e0c53 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.tsx @@ -4,12 +4,8 @@ import { type LoaderFunctionArgs, type MetaFunction, Outlet, - useLoaderData, } from "react-router" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" -import { HeaderFertilizer } from "~/components/blocks/header/fertilizer" import { SidebarInset } from "~/components/ui/sidebar" import { getSession } from "~/lib/auth.server" import { clientConfig } from "~/lib/config" @@ -58,20 +54,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { statusText: "not found: farms", }) } - - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm || "", - } - }) - - // Return user information from loader - return { - farm: farm, - b_id_farm: b_id_farm, - farmOptions: farmOptions, - } } catch (error) { throw handleLoaderError(error) } @@ -84,27 +66,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { * It also renders a main section containing the farm title, description, nested routes via an Outlet, and a notification toaster. */ export default function FarmFertilizerBlock() { - const loaderData = useLoaderData() - return ( -
- - -
-
- - -
-
- -
- - ) + // Get the available fertilizers + const fertilizers = await getFertilizers( + fdm, + session.principal_id, + b_id_farm, + ) + + const fertilizerOptions = fertilizers.map((fertilizer) => { + return { + p_id: fertilizer.p_id, + p_name_nl: fertilizer.p_name_nl || "", + } + }) + + // Return user information from loader + return { + fertilizerOptions: fertilizerOptions, + } + } catch (error) { + throw handleLoaderError(error) + } } diff --git a/fdm-app/app/routes/farm.$b_id_farm.settings.tsx b/fdm-app/app/routes/farm.$b_id_farm.settings.tsx index 2146bc176..628e5a0e7 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.settings.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.settings.tsx @@ -1,4 +1,4 @@ -import { getFarm, getFarms } from "@svenvw/fdm-core" +import { getFarm } from "@svenvw/fdm-core" import { data, type LoaderFunctionArgs, @@ -8,15 +8,12 @@ import { } from "react-router" import { FarmContent } from "~/components/blocks/farm/farm-content" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" import { SidebarInset } from "~/components/ui/sidebar" import { Toaster } from "~/components/ui/sonner" import { getSession } from "~/lib/auth.server" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" -import { useCalendarStore } from "~/store/calendar" // Meta export const meta: MetaFunction = () => { @@ -66,22 +63,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }) } - // Get a list of possible farms of the user - const farms = await getFarms(fdm, session.principal_id) - if (!farms || farms.length === 0) { - throw data("not found: farms", { - status: 404, - statusText: "not found: farms", - }) - } - - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - // Create the items for sidebar page const sidebarPageItems = [ { @@ -104,9 +85,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Return user information from loader return { - farm: farm, - b_id_farm: b_id_farm, - farmOptions: farmOptions, sidebarPageItems: sidebarPageItems, } } catch (error) { @@ -123,22 +101,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { export default function FarmContentBlock() { const loaderData = useLoaderData() - const calendar = useCalendarStore((state) => state.calendar) - return ( -
- -
{ @@ -35,81 +28,18 @@ export const meta: MetaFunction = () => { * * @throws {Response} If the farm ID is not provided. */ -export async function loader({ request, params }: LoaderFunctionArgs) { +export async function loader({ request }: LoaderFunctionArgs) { try { - // Get the farm id - const b_id_farm = params.b_id_farm - if (!b_id_farm) { - throw data("Farm ID is required", { - status: 400, - statusText: "Farm ID is required", - }) - } - // Get the session - const session = await getSession(request) - - // Get a list of possible farms of the user - const farms = await getFarms(fdm, session.principal_id) - if (!farms || farms.length === 0) { - throw data("not found: farms", { - status: 404, - statusText: "not found: farms", - }) - } - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) - - // Get the fields to be selected if available - const timeframe = getTimeframe(params) - let fieldOptions: HeaderFieldOption[] = [] - if (timeframe) { - const fields = await getFields( - fdm, - session.principal_id, - b_id_farm, - timeframe, - ) - fieldOptions = fields.map((field) => { - if (!field?.b_id || !field?.b_name) { - throw new Error("Invalid field data structure") - } - return { - b_id: field.b_id, - b_name: field.b_name, - b_area: Math.round(field.b_area * 10) / 10, - } - }) - } - - // Return the farm ID and session info - return { - farmId: b_id_farm, - farmOptions, - fieldOptions, - session, - } + await getSession(request) } catch (error) { return handleActionError(error) } } export function ErrorBoundary(props: Route.ErrorBoundaryProps) { - const farmFieldOptionsStore = useFarmFieldOptionsStore() - const { params } = props - return ( -
- -
) diff --git a/fdm-app/app/routes/farm._index.tsx b/fdm-app/app/routes/farm._index.tsx index a613ed74f..2825740e6 100644 --- a/fdm-app/app/routes/farm._index.tsx +++ b/fdm-app/app/routes/farm._index.tsx @@ -7,8 +7,6 @@ import { useLoaderData, } from "react-router" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarm } from "~/components/blocks/header/farm" import { Badge } from "~/components/ui/badge" import { Button } from "~/components/ui/button" import { @@ -28,7 +26,6 @@ import { getTimeBasedGreeting } from "~/lib/greetings" import { getCalendarSelection } from "~/lib/calendar" import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" import { Route } from "../+types/root" -import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -53,7 +50,7 @@ export const meta: MetaFunction = () => { * * @throws {Error} If retrieving the session or fetching the farm data fails. */ -export async function loader({ request, params }: LoaderFunctionArgs) { +export async function loader({ request }: LoaderFunctionArgs) { try { // Get the session const session = await getSession(request) @@ -63,17 +60,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Get a list of possible farms of the user const farms = await getFarms(fdm, session.principal_id) - const farmOptions = farms.map((farm) => { - return { - b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, - } - }) // Return user information from loader return { farms: farms, - farmOptions: farmOptions, calendar: calendar, username: session.userName, } @@ -95,12 +85,6 @@ export default function AppIndex() { return ( -
- -
{loaderData.farms.length === 0 ? (
@@ -414,17 +398,8 @@ export default function AppIndex() { } export function ErrorBoundary(props: Route.ErrorBoundaryProps) { - const farmFieldOptionsStore = useFarmFieldOptionsStore() - const { params } = props - return ( -
- -
) diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx index fa1f5f1c5..91ef7d9d3 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx @@ -2,8 +2,6 @@ import { getFarm } from "@svenvw/fdm-core" import { Map as MapIcon, UploadCloud } from "lucide-react" import type { LoaderFunctionArgs, MetaFunction } from "react-router" import { data, NavLink, useLoaderData } from "react-router" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarmCreate } from "~/components/blocks/header/create-farm" import { Accordion, AccordionContent, @@ -20,8 +18,7 @@ import { } from "~/components/ui/card" import { SidebarInset } from "~/components/ui/sidebar" import { clientConfig } from "~/lib/config" -import { getSession } from "../lib/auth.server" -import { fdm } from "../lib/fdm.server" +import { getSession } from "~/lib/auth.server" // Meta export const meta: MetaFunction = () => { @@ -43,27 +40,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) { } // Get the session - const session = await getSession(request) - - const farm = await getFarm(fdm, session.principal_id, b_id_farm) - if (!farm) { - throw data("Farm not found", { - status: 404, - statusText: "Farm not found", - }) - } - - return { farm } + await getSession(request) } export default function ChooseFieldImportMethod() { - const { farm } = useLoaderData() - return ( -
- -

diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx index 0e00483e0..1832c0b81 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.access.tsx @@ -62,7 +62,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { if (!b_id_farm) { throw data("Farm ID is required", { status: 400 }) } - const calendar = getCalendar(params) // Get calendar year const session = await getSession(request) @@ -86,10 +85,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return { b_id_farm: b_id_farm, - b_name_farm: farm.b_name_farm, principals: principals, hasSharePermission: hasSharePermission, - calendar: calendar, } } catch (error) { throw handleLoaderError(error) @@ -100,14 +97,11 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // TODO: Add wizard-specific layout/header/breadcrumbs // TODO: Add "Voltooien" button with correct navigation export default function CreateFarmAccessStep() { - const { b_id_farm, b_name_farm, principals, hasSharePermission, calendar } = + const { b_id_farm, principals, hasSharePermission } = useLoaderData() return ( -
- -
@@ -234,17 +228,7 @@ export async function action({ request, params }: ActionFunctionArgs) { } export function ErrorBoundary(props: Route.ErrorBoundaryProps) { - const { params } = props - const farmFieldOptionsStore = useFarmFieldOptionsStore() - const cachedFarmName = farmFieldOptionsStore.getFarmById( - params.b_id_farm, - )?.b_name_farm - return ( - <> -
- -
@@ -262,6 +246,5 @@ export function ErrorBoundary(props: Route.ErrorBoundaryProps) {
- ) } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx index ffe73382e..c2b673454 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.atlas.tsx @@ -55,7 +55,7 @@ import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleActionError, handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" -import FieldDetailsInfoPopup from "../components/blocks/field/popup" +import FieldDetailsInfoPopup from "~/components/blocks/field/popup" // Meta export const meta: MetaFunction = () => { @@ -162,7 +162,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return { b_id_farm: farm.b_id_farm, - b_name_farm: farm.b_name_farm, fieldsSaved: fieldsSaved, timeframe: timeframe, calendar: calendar, @@ -219,9 +218,6 @@ export default function Index() { return ( -
- -
diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx index 0244212d7..6209a4bd4 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.cultivations.tsx @@ -8,8 +8,6 @@ import { } from "react-router" import { CultivationListPlan } from "~/components/blocks/cultivation/list-plan" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarmCreate } from "~/components/blocks/header/create-farm" import { getSession } from "~/lib/auth.server" import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" @@ -17,7 +15,6 @@ import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import type { Route } from "../+types/root" import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" -import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -104,64 +101,50 @@ export default function Index() { const loaderData = useLoaderData() return ( - <> -
- -
-
- -
-
- -
- -
+
+ +
+
+ +
+
-
- +
+
) } export function ErrorBoundary(props: Route.ErrorBoundaryProps) { const { params } = props - const farmFieldOptionsStore = useFarmFieldOptionsStore() - const cachedFarmName = farmFieldOptionsStore.getFarmById( - params.b_id_farm, - )?.b_name_farm return ( - <> -
- -
-
- - -
- +
+ + +
) } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx index 43cddad4a..f7be07c53 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.tsx @@ -72,22 +72,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { (x: { parameter: string }) => x.parameter === "p_app_method_options", ) if (!applicationMethods) throw new Error("Parameter metadata missing") - // Map fertilizers to options for the combobox - const fertilizerOptions = fertilizers.map((fertilizer) => { - const applicationMethodOptions = fertilizer.p_app_method_options - .map((opt: string) => { - const meta = applicationMethods.options.find( - (x: { value: string }) => x.value === opt, - ) - return meta ? { value: opt, label: meta.label } : undefined - }) - .filter(Boolean) - return { - value: fertilizer.p_id, - label: fertilizer.p_name_nl, - applicationMethodOptions: applicationMethodOptions, - } - }) // Fetch the cultivation plan for the farm const cultivationPlan = await getCultivationPlan( @@ -143,7 +127,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return { b_lu_catalogue: b_lu_catalogue, b_id_farm: b_id_farm, - fertilizerOptions: fertilizerOptions, fertilizerApplications: fertilizerApplications, dose: dose.dose, applicationMethodOptions: applicationMethods.options, diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx index b008585e6..84e3cdd53 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.tsx @@ -1,4 +1,4 @@ -import { getCultivationPlan, getFarm } from "@svenvw/fdm-core" +import { getCultivationPlan, getFarm, getFertilizers } from "@svenvw/fdm-core" import { data, type LoaderFunctionArgs, @@ -8,15 +8,12 @@ import { } from "react-router" import { CultivationListPlan } from "~/components/blocks/cultivation/list-plan" import { FarmTitle } from "~/components/blocks/farm/farm-title" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarmCreate } from "~/components/blocks/header/create-farm" import { getSession } from "~/lib/auth.server" import { getCalendar, getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { handleLoaderError } from "~/lib/error" import { fdm } from "~/lib/fdm.server" import type { Route } from "../+types/root" -import { useFarmFieldOptionsStore } from "~/store/farm-field-options" import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" // Meta @@ -60,6 +57,20 @@ export async function loader({ request, params }: LoaderFunctionArgs) { }) } + // Get the available fertilizers + const fertilizers = await getFertilizers( + fdm, + session.principal_id, + b_id_farm, + ) + + const fertilizerOptions = fertilizers.map((fertilizer) => { + return { + p_id: fertilizer.p_id, + p_name_nl: fertilizer.p_name_nl || "", + } + }) + const cultivationPlan = await getCultivationPlan( fdm, session.principal_id, @@ -70,7 +81,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { return { cultivationPlan: cultivationPlan, b_id_farm: b_id_farm, - b_name_farm: farm.b_name_farm, + fertilizerOptions: fertilizerOptions, calendar: calendar, } } catch (error) { @@ -83,64 +94,46 @@ export default function Index() { const loaderData = useLoaderData() return ( - <> -
- -
-
- -
-
- -
- -
+
+ +
+
+ +
+
-
- +
+
) } export function ErrorBoundary(props: Route.ErrorBoundaryProps) { const { params } = props - const farmFieldOptionsStore = useFarmFieldOptionsStore() - const cachedFarmName = farmFieldOptionsStore.getFarmById( - params.b_id_farm, - )?.b_name_farm return ( - <> -
- -
-
- - -
- +
+ + +
) } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx index f506bc55b..ff2fc30d9 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fields.tsx @@ -8,8 +8,6 @@ import { Outlet, useLoaderData, } from "react-router" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarmCreate } from "~/components/blocks/header/create-farm" import { SidebarPage } from "~/components/custom/sidebar-page" import { Button } from "~/components/ui/button" import { Separator } from "~/components/ui/separator" @@ -22,7 +20,6 @@ import { fdm } from "~/lib/fdm.server" import { cn } from "~/lib/utils" import type { Route } from "../+types/root" import { InlineErrorBoundary } from "~/components/custom/inline-error-boundary" -import { useFarmFieldOptionsStore } from "~/store/farm-field-options" // Meta export const meta: MetaFunction = () => { @@ -111,9 +108,6 @@ export default function Index() { return ( -
- -
@@ -175,17 +169,8 @@ export default function Index() { } export function ErrorBoundary(props: Route.ErrorBoundaryProps) { - const { params } = props - const farmFieldOptionsStore = useFarmFieldOptionsStore() - const cachedFarmName = farmFieldOptionsStore.getFarmById( - params.b_id_farm, - )?.b_name_farm - return ( -
- -
diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx index 95edf6394..e2055a0e8 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.upload.tsx @@ -18,8 +18,6 @@ import { data, useLoaderData } from "react-router" import { dataWithWarning, redirectWithSuccess } from "remix-toast" import { combine, parseDbf, parseShp } from "shpjs" import { MijnPercelenUploadForm } from "@/app/components/blocks/mijnpercelen/form-upload" -import { Header } from "~/components/blocks/header/base" -import { HeaderFarmCreate } from "~/components/blocks/header/create-farm" import { SidebarInset } from "~/components/ui/sidebar" import { getNmiApiKey, getSoilParameterEstimates } from "~/integrations/nmi" import { getSession } from "~/lib/auth.server" @@ -64,17 +62,14 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const calendar = getCalendar(params) - return { b_id_farm, b_name_farm: farm.b_name_farm, calendar } + return { b_id_farm, calendar } } export default function UploadMijnPercelenPage() { - const { b_id_farm, calendar, b_name_farm } = useLoaderData() + const { b_id_farm, calendar } = useLoaderData() return ( -
- -
-
- -
diff --git a/fdm-app/app/routes/farm.tsx b/fdm-app/app/routes/farm.tsx index c6139880f..43fee093a 100644 --- a/fdm-app/app/routes/farm.tsx +++ b/fdm-app/app/routes/farm.tsx @@ -20,6 +20,8 @@ import { handleLoaderError } from "~/lib/error" import { useCalendarStore } from "~/store/calendar" import { useFarmStore } from "~/store/farm" import HeaderAutomatic from "~/components/blocks/header/automatic" +import { getFarms } from "@svenvw/fdm-core" +import { fdm } from "~/lib/fdm.server" export const meta: MetaFunction = () => { return [ @@ -53,12 +55,28 @@ export async function loader({ request }: LoaderFunctionArgs) { return sessionCheckResponse } - // Return user information from loader - return { + const data = { user: session.user, userName: session.userName, initials: session.initials, + farmOptions: [], } + + try { + // Get a list of possible farms of the user + const farms = await getFarms(fdm, session.principal_id) + const farmOptions = farms.map((farm) => { + return { + b_id_farm: farm.b_id_farm, + b_name_farm: farm.b_name_farm, + } + }) + + data.farmOptions = farmOptions + } catch (_) {} + + // Return user information from loader + return data } catch (error) { // If getSession throws (e.g., invalid token), it might result in a 401 // We need to handle that case here as well, similar to the ErrorBoundary