From aaa2f0b3bcf6134ba8676aa76b4b7725d6c29baa Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:05:04 +0200 Subject: [PATCH 01/10] feat: show card skeletons when data is loading for advice --- .../blocks/nutrient-advice/skeletons.tsx | 58 ++++++++ ...d_farm.$calendar.nutrient_advice.$b_id.tsx | 139 +++++++++++------- 2 files changed, 142 insertions(+), 55 deletions(-) create mode 100644 fdm-app/app/components/blocks/nutrient-advice/skeletons.tsx diff --git a/fdm-app/app/components/blocks/nutrient-advice/skeletons.tsx b/fdm-app/app/components/blocks/nutrient-advice/skeletons.tsx new file mode 100644 index 000000000..56607bd30 --- /dev/null +++ b/fdm-app/app/components/blocks/nutrient-advice/skeletons.tsx @@ -0,0 +1,58 @@ +import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card" +import { Progress } from "~/components/ui/progress" +import { Separator } from "~/components/ui/separator" + +export function NutrientCardSkeleton() { + return ( + + +
+
+
+ +
+
+ + +
+
+
+
+
+
+ + +
+ +
+
+ +
+ {[...Array(2)].map((_, i) => ( +
+
+

+

+

+
+

+

+

+
+ ))} +
+
+ + + ) +} + +export function NutrientAdviceFallback() { + 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 82cb7a790..1358ba662 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 @@ -7,7 +7,9 @@ import { getField, } from "@svenvw/fdm-core" import { Tally1, Tally2, Tally3 } from "lucide-react" +import { Suspense } from "react" import { + Await, type LoaderFunctionArgs, type MetaFunction, useLoaderData, @@ -21,7 +23,10 @@ import { } from "~/components/blocks/nutrient-advice/kpi" import { getNutrientsDescription } from "~/components/blocks/nutrient-advice/nutrients" import type { NutrientDescription } from "~/components/blocks/nutrient-advice/types" -import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { + NutrientAdviceFallback, + NutrientCardSkeleton, +} from "~/components/blocks/nutrient-advice/skeletons" import { Card, CardContent, @@ -118,7 +123,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const b_lu_catalogue = cultivations[0].b_lu_catalogue // Request nutrient advice - const nutrientAdvice = await getNutrientAdvice( + const nutrientAdvice = getNutrientAdvice( b_lu_catalogue, field.b_centroid, currentSoilData, @@ -161,9 +166,9 @@ export default function FieldNutrientAdviceBlock() { const traceNutrients = nutrientsDescription.filter( (item: NutrientDescription) => item.type === "trace", ) - // console.log(primaryNutrients) + return ( -
+
@@ -177,28 +182,36 @@ export default function FieldNutrientAdviceBlock() { {navigation.state === "loading" ? ( -
- -
+ ) : (
{primaryNutrients.map( (nutrient: NutrientDescription) => ( - + fallback={} + > + + {(nutrientAdvice) => ( + + )} + + ), )}
@@ -237,28 +250,36 @@ export default function FieldNutrientAdviceBlock() { {navigation.state === "loading" ? ( -
- -
+ ) : (
{secondaryNutrients.map( (nutrient: NutrientDescription) => ( - + fallback={} + > + + {(nutrientAdvice) => ( + + )} + + ), )}
@@ -278,28 +299,36 @@ export default function FieldNutrientAdviceBlock() { {navigation.state === "loading" ? ( -
- -
+ ) : (
{traceNutrients.map( (nutrient: NutrientDescription) => ( - + fallback={} + > + + {(nutrientAdvice) => ( + + )} + + ), )}
From a2be95c0209054189e52a4e1adcc9d7dbf14586a Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:20:50 +0200 Subject: [PATCH 02/10] fix: typo --- fdm-app/app/components/blocks/norms/field-norms.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdm-app/app/components/blocks/norms/field-norms.tsx b/fdm-app/app/components/blocks/norms/field-norms.tsx index 99791ae6e..f8d31b94d 100644 --- a/fdm-app/app/components/blocks/norms/field-norms.tsx +++ b/fdm-app/app/components/blocks/norms/field-norms.tsx @@ -36,7 +36,7 @@ export function FieldNorms({ fieldNorms, fieldOptions }: FieldNormsProps) { return (
-

Perceelniveau

+

Perceelsniveau

{fieldNorms.map((field) => ( Date: Wed, 23 Jul 2025 16:21:56 +0200 Subject: [PATCH 03/10] feat: show card skeletons when data is loading for norms --- .../app/components/blocks/norms/skeletons.tsx | 95 ++++++++++ .../farm.$b_id_farm.$calendar.norms.tsx | 171 +++++++++--------- 2 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 fdm-app/app/components/blocks/norms/skeletons.tsx diff --git a/fdm-app/app/components/blocks/norms/skeletons.tsx b/fdm-app/app/components/blocks/norms/skeletons.tsx new file mode 100644 index 000000000..6720aebe0 --- /dev/null +++ b/fdm-app/app/components/blocks/norms/skeletons.tsx @@ -0,0 +1,95 @@ +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card" +import { Separator } from "~/components/ui/separator" + +export function FarmNormsSkeleton() { + return ( +
+

+
+ + + + + +
+ + + + + + + +
+ + + + + + + +
+ + +
+
+ ) +} + +export function FieldNormsSkeleton() { + return ( +
+

+
+ {[...Array(3)].map((_, i) => ( + + +
+ + +
+
+ +
+
+

+

+

+
+

+

+
+
+
+

+

+

+
+

+

+
+
+
+

+

+

+
+

+

+
+
+
+ ))} +
+

+ ) +} + +export function NormsFallback() { + return ( +
+ + + +
+ ) +} 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 a45686810..421597e5b 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 @@ -29,6 +29,9 @@ import { AlertTriangle } from "lucide-react" import { Button } from "../components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card" import { HeaderNorms } from "../components/blocks/header/norms" +import { NormsFallback } from "~/components/blocks/norms/skeletons" +import { Suspense } from "react" +import { Await } from "react-router" // Meta export const meta: MetaFunction = () => { @@ -211,90 +214,96 @@ export default function FarmNormsBlock() { "Bekijk de gebruiksnormen voor je bedrijf en percelen." } /> - {/* Disclaimer */} - {loaderData.farmNorms && loaderData.fieldNorms ? ( -
- - - - Disclaimer: Deze getallen zijn - uitsluitend bedoeld voor informatieve - doeleinden. De getoonde gebruiksnormen zijn - indicatief en dienen te worden geverifieerd voor - juridische naleving. Raadpleeg altijd de - officiƫle RVO-publicaties en uw adviseur voor - definitieve normen. - - -
- ) : null} + }> + + {([farmNorms, fieldNorms]) => { + if (loaderData.errorMessage) { + return ( +
+ + + + Helaas is het niet mogelijk om je + gebruiksnormen uit te rekenen + + + +
+

+ Er is onverwacht wat misgegaan. + Probeer opnieuw of neem contact op + met Ondersteuning en deel de + volgende foutmelding: +

+
+
+                                                            {JSON.stringify(
+                                                                {
+                                                                    message:
+                                                                        loaderData.errorMessage,
+                                                                    page: page,
+                                                                    timestamp: new Date(),
+                                                                },
+                                                                null,
+                                                                2,
+                                                            )}
+                                                        
+
+
+
+
+
+ ) + } + + if (farmNorms && fieldNorms) { + return ( +
+ + + + Disclaimer: Deze getallen zijn + uitsluitend bedoeld voor informatieve + doeleinden. De getoonde gebruiksnormen zijn + indicatief en dienen te worden geverifieerd voor + juridische naleving. Raadpleeg altijd de + officiƫle RVO-publicaties en uw adviseur voor + definitieve normen. + + + + + +
+ ) + } -
- {loaderData.errorMessage ? ( -
- - - - Helaas is het niet mogelijk om je - gebruiksnormen uit te rekenen - - - -
-

- Er is onverwacht wat misgegaan. - Probeer opnieuw of neem contact op - met Ondersteuning en deel de - volgende foutmelding: + return ( +

+
+

+ Helaas, nog geen gebruiksnormen beschikbaar + voor {loaderData.calendar} +

+

+ Op dit moment kunnen we alleen nog de + gebruiksnormen voor 2025 berekenen en + weergeven.

-
-
-                                                {JSON.stringify(
-                                                    {
-                                                        message:
-                                                            loaderData.errorMessage,
-                                                        page: page,
-                                                        timestamp: new Date(),
-                                                    },
-                                                    null,
-                                                    2,
-                                                )}
-                                            
-
+ + +
- - -
- ) : loaderData.farmNorms && loaderData.fieldNorms ? ( - <> - - - - - ) : ( -
-
-

- Helaas, nog geen gebruiksnormen beschikbaar - voor {loaderData.calendar} -

-

- Op dit moment kunnen we alleen nog de - gebruiksnormen voor 2025 berekenen en - weergeven. -

- - - -
-
- )} -
+
+ ) + }} + + ) From 1bee89d42266ade528c67a8732951771c0b4894f Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:33:49 +0200 Subject: [PATCH 04/10] feat: show card skeletons when data is loading for balance --- .../components/blocks/balance/skeletons.tsx | 77 +++ ..._farm.$calendar.balance.nitrogen.$b_id.tsx | 490 ++++++++--------- ...farm.$calendar.balance.nitrogen._index.tsx | 494 ++++++++---------- 3 files changed, 540 insertions(+), 521 deletions(-) create mode 100644 fdm-app/app/components/blocks/balance/skeletons.tsx diff --git a/fdm-app/app/components/blocks/balance/skeletons.tsx b/fdm-app/app/components/blocks/balance/skeletons.tsx new file mode 100644 index 000000000..d1b2299de --- /dev/null +++ b/fdm-app/app/components/blocks/balance/skeletons.tsx @@ -0,0 +1,77 @@ +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card" +import { Skeleton } from "~/components/ui/skeleton" + +export function NitrogenBalanceCardSkeleton() { + return ( + + + + + + +
+

+ + + ) +} + +export function NitrogenBalanceChartSkeleton() { + return ( + + + + + + + + + + + + ) +} + +export function NitrogenBalanceFieldsSkeleton() { + return ( + + + + + + +

+ {[...Array(4)].map((_, i) => ( +
+ +
+ + +
+
+ +
+
+ ))} +
+ + + ) +} + +export function NitrogenBalanceFallback() { + return ( +
+
+ + + + +
+
+ + +
+
+ ) +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx index 4b9eee720..73a991f04 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx @@ -23,7 +23,7 @@ import { } from "react-router" import { NitrogenBalanceChart } from "~/components/blocks/balance/nitrogen-chart" import NitrogenBalanceDetails from "~/components/blocks/balance/nitrogen-details" -import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { NitrogenBalanceFallback } from "~/components/blocks/balance/skeletons" import { Button } from "~/components/ui/button" import { Card, @@ -33,8 +33,9 @@ import { CardHeader, CardTitle, } from "~/components/ui/card" -import { Skeleton } from "~/components/ui/skeleton" import { getSession } from "~/lib/auth.server" +import { Suspense } from "react" +import { Await } from "react-router" import { getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { fdm } from "~/lib/fdm.server" @@ -106,11 +107,11 @@ export async function loader({ request, params }: LoaderFunctionArgs) { await calculateNitrogenBalance(nitrogenBalanceInput) nitrogenBalanceResult = nitrogenBalanceResult.fields.find( - (field) => field.b_id === b_id, + (field: NitrogenBalanceNumeric['fields'][number]) => field.b_id === b_id, ) nitrogenBalanceInput = nitrogenBalanceInput.fields.find( - (field) => field.field.b_id === b_id, + (field: typeof nitrogenBalanceInput.fields[number]) => field.field.b_id === b_id, ) } catch (error) { errorMessage = String(error).replace("Error: ", "") @@ -130,8 +131,6 @@ export default function FarmBalanceNitrogenFieldBlock() { const location = useLocation() const navigation = useNavigation() const page = location.pathname - const isLoading = navigation.state === "loading" - const calendar = useCalendarStore((state) => state.calendar) const { @@ -144,265 +143,238 @@ export default function FarmBalanceNitrogenFieldBlock() { return (
- {nitrogenBalanceResult ? ( - <> -
- - - - Overschot / Doel (Perceel) - - - - -
- {isLoading ? ( - - ) : ( -
-

- {`${nitrogenBalanceResult.balance} / ${nitrogenBalanceResult.target}`} -

- {nitrogenBalanceResult.balance <= - nitrogenBalanceResult.target ? ( - - ) : ( - - )} -
- )} -
-

- kg N / ha -

-
-
- - - - Aanvoer - - - - -
- {isLoading ? ( - - ) : ( - nitrogenBalanceResult.supply.total - )} -
-

- kg N / ha -

-
-
- - - - Afvoer - - - - -
- {isLoading ? ( - - ) : ( - nitrogenBalanceResult.removal.total - )} + }> + + {(resolvedNitrogenBalanceResult) => { + if (!nitrogenBalanceInput) { + return ( +
+ + + Ongeldig jaar + + +
+

+ Dit perceel was niet in gebruik voor dit + jaar. Als dit perceel wel in gebruik was, + werk dan de startdatum bij in de + perceelsinstelling. +

+
+
+ + + + + +
-

- kg N / ha -

- - - - - - Emissie - - - - -
- {isLoading ? ( - - ) : ( - nitrogenBalanceResult.volatilization - .total - )} -
-

- kg N / ha -

-
-
-
-
- - - Balans - - De stikstofbalans voor {field.b_name} van{" "} - {farm.b_name_farm}. De balans is het - verschil tussen de totale aanvoer, afvoer en - emissie van stikstof. Een positieve balans - betekent een overschot aan stikstof, een - negatieve balans een tekort. - - - - {isLoading ? ( -
- -
- ) : ( - - )} -
-
- - - Posten - - - -
- {isLoading ? ( - [...Array(5)].map((_, index) => { - return ( -
-
- -
-
- + ) + } + + if (errorMessage) { + return ( +
+ + + + Helaas is het niet mogelijk om je balans uit te + rekenen + + + + {!errorMessage ? ( +
+

+ Er is een onbekende fout opgetreden. + Probeer opnieuw of neem contact op met + Ondersteuning. +

+
+ ) : errorMessage.match( + /Missing required soil parameters/, + ) ? ( +
+

+ Voor niet alle percelen zijn de + benodigde bodemparameters bekend: +

+
+
    + {errorMessage.match(/a_n_rt/) ? ( +
  • Totaal stikstofgehalte
  • + ) : null} + {errorMessage.match( + /b_soiltype_agr/, + ) ? ( +
  • Agrarisch bodemtype
  • + ) : null} + {errorMessage.match( + /a_c_of|a_som_loi/, + ) ? ( +
  • Organische stofgehalte
  • + ) : null} +
+
+ ) : ( +
+

+ Er is helaas wat misgegaan. Probeer + opnieuw of neem contact op met + Ondersteuning en deel de volgende + foutmelding: +

+
+
+                                                            {JSON.stringify(
+                                                                {
+                                                                    message: errorMessage,
+                                                                    page: page,
+                                                                    timestamp: new Date(),
+                                                                },
+                                                                null,
+                                                                2,
+                                                            )}
+                                                        
- ) - }) - ) : ( - - )} -
- - -
- - ) : !nitrogenBalanceInput ? ( -
- - - Ongeldig jaar - - -
-

- Dit perceel was niet in gebruik voor dit - jaar. Als dit perceel wel in gebruik was, - werk dan de startdatum bij in de - perceelsinstelling. -

-
-
- - - - - -
-
- ) : ( -
- - - - Helaas is het niet mogelijk om je balans uit te - rekenen - - - - {!errorMessage ? ( -
-

- Er is een onbekende fout opgetreden. - Probeer opnieuw of neem contact op met - Ondersteuning. -

+ )} + +
- ) : errorMessage.match( - /Missing required soil parameters/, - ) ? ( -
-

- Voor niet alle percelen zijn de - benodigde bodemparameters bekend: -

-
-
    - {errorMessage.match(/a_n_rt/) ? ( -
  • Totaal stikstofgehalte
  • - ) : null} - {errorMessage.match( - /b_soiltype_agr/, - ) ? ( -
  • Agrarisch bodemtype
  • - ) : null} - {errorMessage.match( - /a_c_of|a_som_loi/, - ) ? ( -
  • Organische stofgehalte
  • - ) : null} -
+ ) + } + + return ( + <> +
+ + + + Overschot / Doel (Perceel) + + + + +
+
+

+ {`${resolvedNitrogenBalanceResult.balance} / ${resolvedNitrogenBalanceResult.target}`} +

+ {resolvedNitrogenBalanceResult.balance <= + resolvedNitrogenBalanceResult.target ? ( + + ) : ( + + )} +
+
+

+ kg N / ha +

+
+
+ + + + Aanvoer + + + + +
+ {resolvedNitrogenBalanceResult.supply.total} +
+

+ kg N / ha +

+
+
+ + + + Afvoer + + + + +
+ {resolvedNitrogenBalanceResult.removal.total} +
+

+ kg N / ha +

+
+
+ + + + Emissie + + + + +
+ {resolvedNitrogenBalanceResult.volatilization.total} +
+

+ kg N / ha +

+
+
- ) : ( -
-

- Er is helaas wat misgegaan. Probeer - opnieuw of neem contact op met - Ondersteuning en deel de volgende - foutmelding: -

-
-
-                                            {JSON.stringify(
-                                                {
-                                                    message: errorMessage,
-                                                    page: page,
-                                                    timestamp: new Date(),
-                                                },
-                                                null,
-                                                2,
-                                            )}
-                                        
-
+
+ + + Balans + + De stikstofbalans voor {field.b_name} van{" "} + {farm.b_name_farm}. De balans is het + verschil tussen de totale aanvoer, afvoer en + emissie van stikstof. Een positieve balans + betekent een overschot aan stikstof, een + negatieve balans een tekort. + + + + + + + + + Posten + + + +
+ +
+
+
- )} - - -
- )} + + ) + }} + +
) } diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx index 840185fa7..dd0090177 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen._index.tsx @@ -22,7 +22,7 @@ import { useNavigation, } from "react-router" import { NitrogenBalanceChart } from "~/components/blocks/balance/nitrogen-chart" -import { LoadingSpinner } from "~/components/custom/loadingspinner" +import { NitrogenBalanceFallback } from "~/components/blocks/balance/skeletons" import { Card, CardContent, @@ -30,8 +30,9 @@ import { CardHeader, CardTitle, } from "~/components/ui/card" -import { Skeleton } from "~/components/ui/skeleton" import { getSession } from "~/lib/auth.server" +import { Suspense } from "react" +import { Await } from "react-router" import { getTimeframe } from "~/lib/calendar" import { clientConfig } from "~/lib/config" import { fdm } from "~/lib/fdm.server" @@ -109,276 +110,245 @@ export default function FarmBalanceNitrogenOverviewBlock() { const location = useLocation() const navigation = useNavigation() const page = location.pathname - const isLoading = navigation.state === "loading" - const { nitrogenBalanceResult, farm, fields, errorMessage } = loaderData const fieldsMap = new Map(fields.map((f) => [f.b_id, f])) return (
- {nitrogenBalanceResult ? ( - <> -
- - - - Overschot / Doel (Bedrijf) - - - - -
- {isLoading ? ( - - ) : ( -
-

- {`${nitrogenBalanceResult.balance} / ${nitrogenBalanceResult.target}`} -

- {nitrogenBalanceResult.balance <= - nitrogenBalanceResult.target ? ( - + }> + + {(resolvedNitrogenBalanceResult) => { + if (errorMessage) { + return ( +
+ + + + Helaas is het niet mogelijk om je balans uit te + rekenen + + + + {!errorMessage ? ( +
+

+ Er is een onbekende fout opgetreden. + Probeer opnieuw of neem contact op met + Ondersteuning. +

+
+ ) : errorMessage.match( + /Missing required soil parameters/, + ) ? ( +
+

+ Voor niet alle percelen zijn de + benodigde bodemparameters bekend: +

+
+
    + {errorMessage.match(/a_n_rt/) ? ( +
  • Totaal stikstofgehalte
  • + ) : null} + {errorMessage.match( + /b_soiltype_agr/, + ) ? ( +
  • Agrarisch bodemtype
  • + ) : null} + {errorMessage.match( + /a_c_of|a_som_loi/, + ) ? ( +
  • Organische stofgehalte
  • + ) : null} +
+
) : ( - +
+

+ Er is helaas wat misgegaan. Probeer + opnieuw of neem contact op met + Ondersteuning en deel de volgende + foutmelding: +

+
+
+                                                            {JSON.stringify(
+                                                                {
+                                                                    message: errorMessage,
+                                                                    page: page,
+                                                                    timestamp: new Date(),
+                                                                },
+                                                                null,
+                                                                2,
+                                                            )}
+                                                        
+
+
)} -
- )} -
-

- kg N / ha -

- - - - - - Aanvoer - - - - -
- {isLoading ? ( - - ) : ( - nitrogenBalanceResult.supply - )} -
-

- kg N / ha -

-
-
- - - - Afvoer - - - - -
- {isLoading ? ( - - ) : ( - nitrogenBalanceResult.removal - )} + +
-

- kg N / ha -

-
-
- - - - Emissie - - - - -
- {isLoading ? ( - - ) : ( - nitrogenBalanceResult.volatilization - )} -
-

- kg N / ha -

-
-
-
-
- - - Balans - - De stikstofbalans voor alle percelen van{" "} - {farm.b_name_farm}. De balans is het - verschil tussen de totale aanvoer, afvoer en - emissie van stikstof. Een positieve balans - betekent een overschot aan stikstof, een - negatieve balans een tekort. - - - - {isLoading ? ( -
- -
- ) : ( - - )} - {/* */} -
-
- - - Percelen - - - -
- {isLoading - ? fields.map((field) => { - return ( -
-
- -
-
- -
-
- ) - }) - : nitrogenBalanceResult.fields.map( - (field) => { - const fieldData = - fieldsMap.get(field.b_id) - return ( -
- {field.balance <= - field.target ? ( - - ) : ( - - )} + ) + } -
- -

- { - fieldData?.b_name - } -

-
-

- { - fieldData?.b_area - }{" "} - ha -

-
-
- {field.balance} /{" "} - {field.target} -
-
- ) - }, - )} -
-
-
-
- - ) : ( -
- - - - Helaas is het niet mogelijk om je balans uit te - rekenen - - - - {!errorMessage ? ( -
-

- Er is een onbekende fout opgetreden. - Probeer opnieuw of neem contact op met - Ondersteuning. -

-
- ) : errorMessage.match( - /Missing required soil parameters/, - ) ? ( -
-

- Voor niet alle percelen zijn de - benodigde bodemparameters bekend: -

-
-
    - {errorMessage.match(/a_n_rt/) ? ( -
  • Totaal stikstofgehalte
  • - ) : null} - {errorMessage.match( - /b_soiltype_agr/, - ) ? ( -
  • Agrarisch bodemtype
  • - ) : null} - {errorMessage.match( - /a_c_of|a_som_loi/, - ) ? ( -
  • Organische stofgehalte
  • - ) : null} -
+ return ( + <> +
+ + + + Overschot / Doel (Bedrijf) + + + + +
+
+

+ {`${resolvedNitrogenBalanceResult.balance} / ${resolvedNitrogenBalanceResult.target}`} +

+ {resolvedNitrogenBalanceResult.balance <= + resolvedNitrogenBalanceResult.target ? ( + + ) : ( + + )} +
+
+

+ kg N / ha +

+
+
+ + + + Aanvoer + + + + +
+ {resolvedNitrogenBalanceResult.supply} +
+

+ kg N / ha +

+
+
+ + + + Afvoer + + + + +
+ {resolvedNitrogenBalanceResult.removal} +
+

+ kg N / ha +

+
+
+ + + + Emissie + + + + +
+ {resolvedNitrogenBalanceResult.volatilization} +
+

+ kg N / ha +

+
+
- ) : ( -
-

- Er is helaas wat misgegaan. Probeer - opnieuw of neem contact op met - Ondersteuning en deel de volgende - foutmelding: -

-
-
-                                            {JSON.stringify(
-                                                {
-                                                    message: errorMessage,
-                                                    page: page,
-                                                    timestamp: new Date(),
-                                                },
-                                                null,
-                                                2,
-                                            )}
-                                        
-
+
+ + + Balans + + De stikstofbalans voor alle percelen van{" "} + {farm.b_name_farm}. De balans is het + verschil tussen de totale aanvoer, afvoer en + emissie van stikstof. Een positieve balans + betekent een overschot aan stikstof, een + negatieve balans een tekort. + + + + + + + + + Percelen + + + +
+ {resolvedNitrogenBalanceResult.fields.map( + (field: NitrogenBalanceNumeric['fields'][number]) => { + const fieldData = + fieldsMap.get(field.b_id) + return ( +
+ {field.balance <= + field.target ? ( + + ) : ( + + )} + +
+ +

+ { + fieldData?.b_name + } +

+
+

+ { + fieldData?.b_area + }{" "} + ha +

+
+
+ {field.balance} /{" "} + {field.target} +
+
+ ) + }, + )} +
+
+
- )} - - -
- )} + + ) + }} + +
) } From 21517f8f99ee6466898df0455f3b28f22612664b Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:43:55 +0200 Subject: [PATCH 05/10] refactor: remove not needed navigation state check --- ...d_farm.$calendar.nutrient_advice.$b_id.tsx | 200 ++++++++---------- 1 file changed, 93 insertions(+), 107 deletions(-) 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 1358ba662..89a978cab 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 @@ -13,7 +13,6 @@ import { type LoaderFunctionArgs, type MetaFunction, useLoaderData, - useNavigation, } from "react-router" import { NutrientCard } from "~/components/blocks/nutrient-advice/cards" import { @@ -155,7 +154,6 @@ export default function FieldNutrientAdviceBlock() { fertilizers, calendar, } = useLoaderData() - const navigation = useNavigation() const primaryNutrients = nutrientsDescription.filter( (item: NutrientDescription) => item.type === "primary", @@ -181,41 +179,37 @@ export default function FieldNutrientAdviceBlock() { - {navigation.state === "loading" ? ( - - ) : ( -
- {primaryNutrients.map( - (nutrient: NutrientDescription) => ( - } - > - - {(nutrientAdvice) => ( - - )} - - - ), - )} -
- )} +
+ {primaryNutrients.map( + (nutrient: NutrientDescription) => ( + } + > + + {(nutrientAdvice) => ( + + )} + + + ), + )} +
@@ -249,41 +243,37 @@ export default function FieldNutrientAdviceBlock() { - {navigation.state === "loading" ? ( - - ) : ( -
- {secondaryNutrients.map( - (nutrient: NutrientDescription) => ( - } - > - - {(nutrientAdvice) => ( - - )} - - - ), - )} -
- )} +
+ {secondaryNutrients.map( + (nutrient: NutrientDescription) => ( + } + > + + {(nutrientAdvice) => ( + + )} + + + ), + )} +
@@ -298,41 +288,37 @@ export default function FieldNutrientAdviceBlock() { - {navigation.state === "loading" ? ( - - ) : ( -
- {traceNutrients.map( - (nutrient: NutrientDescription) => ( - } - > - - {(nutrientAdvice) => ( - - )} - - - ), - )} -
- )} +
+ {traceNutrients.map( + (nutrient: NutrientDescription) => ( + } + > + + {(nutrientAdvice) => ( + + )} + + + ), + )} +
From b7d95e0e6a63bb8bf65db331efeb908e975853d5 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:45:40 +0200 Subject: [PATCH 06/10] chore: add changeset --- .changeset/giant-files-yell.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/giant-files-yell.md diff --git a/.changeset/giant-files-yell.md b/.changeset/giant-files-yell.md new file mode 100644 index 000000000..6d41f3dac --- /dev/null +++ b/.changeset/giant-files-yell.md @@ -0,0 +1,5 @@ +--- +"@svenvw/fdm-app": minor +--- + +Add for pages with calculations (.e.g., nutrient advice, norms and balance) placeholders with skeletons so that user sees the page already and is notified that the content will arrive shortly From 0145296770bfba4936e652a52f1b0a415b784c76 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:41:08 +0200 Subject: [PATCH 07/10] fix: import --- fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx | 1 + 1 file changed, 1 insertion(+) 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 8452c26cf..26e27cd18 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 @@ -51,6 +51,7 @@ import { handleActionError, handleLoaderError } from "~/lib/error"; import { fdm } from "~/lib/fdm.server"; import { extractFormValuesFromRequest } from "~/lib/form"; import { useCalendarStore } from "~/store/calendar"; +import { clientConfig } from "~/lib/config"; // Meta export const meta: MetaFunction = () => { From f5f44f39b16ad9f3a4881d83e1bafd8d77cffd87 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:41:46 +0200 Subject: [PATCH 08/10] refactor: improve implementation of suspense --- ..._farm.$calendar.balance.nitrogen.$b_id.tsx | 66 ++++++++----------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx index 73a991f04..c0b99a990 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx @@ -92,53 +92,42 @@ export async function loader({ request, params }: LoaderFunctionArgs) { const field = await getField(fdm, session.principal_id, b_id) // Collect input data for nutrient balance calculation - let nitrogenBalanceInput = await collectInputForNitrogenBalance( + const nitrogenBalancePromise = collectInputForNitrogenBalance( fdm, session.principal_id, b_id_farm, timeframe, String(process.env.FDM_PUBLIC_DATA_URL), - ) - - let nitrogenBalanceResult = null as NitrogenBalanceNumeric | null - let errorMessage = null as string | null - try { - nitrogenBalanceResult = - await calculateNitrogenBalance(nitrogenBalanceInput) - - nitrogenBalanceResult = nitrogenBalanceResult.fields.find( - (field: NitrogenBalanceNumeric['fields'][number]) => field.b_id === b_id, - ) - - nitrogenBalanceInput = nitrogenBalanceInput.fields.find( - (field: typeof nitrogenBalanceInput.fields[number]) => field.field.b_id === b_id, - ) - } catch (error) { - errorMessage = String(error).replace("Error: ", "") - } + ).then(async (input) => { + const result = await calculateNitrogenBalance(input); + return { + input: input.fields.find((field: { field: { b_id: string } }) => field.field.b_id === b_id), + result: result.fields.find((field: { b_id: string }) => field.b_id === b_id), + errorMessage: null + }; + }).catch(error => ({ + input: null, + result: null, + errorMessage: String(error).replace("Error: ", "") + })); return { - nitrogenBalanceInput: nitrogenBalanceInput, - nitrogenBalanceResult: nitrogenBalanceResult, + nitrogenBalanceResult: nitrogenBalancePromise, field: field, farm: farm, - errorMessage: errorMessage, } } export default function FarmBalanceNitrogenFieldBlock() { const loaderData = useLoaderData() const location = useLocation() - const navigation = useNavigation() const page = location.pathname const calendar = useCalendarStore((state) => state.calendar) const { - nitrogenBalanceInput, nitrogenBalanceResult, field, farm, - errorMessage, } = loaderData return ( @@ -146,7 +135,8 @@ export default function FarmBalanceNitrogenFieldBlock() { }> {(resolvedNitrogenBalanceResult) => { - if (!nitrogenBalanceInput) { + const { input, result, errorMessage } = resolvedNitrogenBalanceResult + if (!input) { return (
@@ -262,10 +252,10 @@ export default function FarmBalanceNitrogenFieldBlock() {

- {`${resolvedNitrogenBalanceResult.balance} / ${resolvedNitrogenBalanceResult.target}`} + {`${result.balance} / ${result.target}`}

- {resolvedNitrogenBalanceResult.balance <= - resolvedNitrogenBalanceResult.target ? ( + {result.balance <= + result.target ? ( ) : ( @@ -286,7 +276,7 @@ export default function FarmBalanceNitrogenFieldBlock() {
- {resolvedNitrogenBalanceResult.supply.total} + {result.supply.total}

kg N / ha @@ -302,7 +292,7 @@ export default function FarmBalanceNitrogenFieldBlock() {

- {resolvedNitrogenBalanceResult.removal.total} + {result.removal.total}

kg N / ha @@ -318,7 +308,7 @@ export default function FarmBalanceNitrogenFieldBlock() {

- {resolvedNitrogenBalanceResult.volatilization.total} + {result.volatilization.total}

kg N / ha @@ -341,15 +331,15 @@ export default function FarmBalanceNitrogenFieldBlock() { @@ -363,8 +353,8 @@ export default function FarmBalanceNitrogenFieldBlock() {

From 012bdd76b6fe62ebb84491fe6a2f04794ebb2810 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:49:40 +0200 Subject: [PATCH 09/10] refactor: prevent repeated promise resolution --- ...d_farm.$calendar.nutrient_advice.$b_id.tsx | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) 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 89a978cab..823dbbdd3 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 @@ -180,21 +180,21 @@ export default function FieldNutrientAdviceBlock() {
- {primaryNutrients.map( - (nutrient: NutrientDescription) => ( - } - > - - {(nutrientAdvice) => ( + ( + + ))} + > + + {(nutrientAdvice) => + primaryNutrients.map( + (nutrient: NutrientDescription) => ( - )} - - - ), - )} + ), + ) + } + +
@@ -244,21 +244,21 @@ export default function FieldNutrientAdviceBlock() {
- {secondaryNutrients.map( - (nutrient: NutrientDescription) => ( - } - > - - {(nutrientAdvice) => ( + ( + + ))} + > + + {(nutrientAdvice) => + secondaryNutrients.map( + (nutrient: NutrientDescription) => ( - )} - - - ), - )} + ), + ) + } + +
@@ -289,21 +289,21 @@ export default function FieldNutrientAdviceBlock() {
- {traceNutrients.map( - (nutrient: NutrientDescription) => ( - } - > - - {(nutrientAdvice) => ( + ( + + ))} + > + + {(nutrientAdvice) => + traceNutrients.map( + (nutrient: NutrientDescription) => ( - )} - - - ), - )} + ), + ) + } + +
) -} +} \ No newline at end of file From 37b80b8c5490a03d19578beb353c552bd1251207 Mon Sep 17 00:00:00 2001 From: Sven Verweij <37927107+SvenVw@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:02:31 +0200 Subject: [PATCH 10/10] docs: improve comment --- ..._farm.$calendar.balance.nitrogen.$b_id.tsx | 135 ++++++++++-------- 1 file changed, 79 insertions(+), 56 deletions(-) diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx index c0b99a990..83e05ba32 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.balance.nitrogen.$b_id.tsx @@ -91,25 +91,32 @@ export async function loader({ request, params }: LoaderFunctionArgs) { // Get details of field const field = await getField(fdm, session.principal_id, b_id) - // Collect input data for nutrient balance calculation - const nitrogenBalancePromise = collectInputForNitrogenBalance( + // Return promise directly for React Router v7 Suspense pattern + const nitrogenBalancePromise = collectInputForNitrogenBalance( fdm, session.principal_id, b_id_farm, timeframe, String(process.env.FDM_PUBLIC_DATA_URL), - ).then(async (input) => { - const result = await calculateNitrogenBalance(input); - return { - input: input.fields.find((field: { field: { b_id: string } }) => field.field.b_id === b_id), - result: result.fields.find((field: { b_id: string }) => field.b_id === b_id), - errorMessage: null - }; - }).catch(error => ({ - input: null, - result: null, - errorMessage: String(error).replace("Error: ", "") - })); + ) + .then(async (input) => { + const result = await calculateNitrogenBalance(input) + return { + input: input.fields.find( + (field: { field: { b_id: string } }) => + field.field.b_id === b_id, + ), + result: result.fields.find( + (field: { b_id: string }) => field.b_id === b_id, + ), + errorMessage: null, + } + }) + .catch((error) => ({ + input: null, + result: null, + errorMessage: String(error).replace("Error: ", ""), + })) return { nitrogenBalanceResult: nitrogenBalancePromise, @@ -124,18 +131,15 @@ export default function FarmBalanceNitrogenFieldBlock() { const page = location.pathname const calendar = useCalendarStore((state) => state.calendar) - const { - nitrogenBalanceResult, - field, - farm, - } = loaderData + const { nitrogenBalanceResult, field, farm } = loaderData return (
}> {(resolvedNitrogenBalanceResult) => { - const { input, result, errorMessage } = resolvedNitrogenBalanceResult + const { input, result, errorMessage } = + resolvedNitrogenBalanceResult if (!input) { return (
@@ -146,9 +150,11 @@ export default function FarmBalanceNitrogenFieldBlock() {

- Dit perceel was niet in gebruik voor dit - jaar. Als dit perceel wel in gebruik was, - werk dan de startdatum bij in de + Dit perceel was niet in + gebruik voor dit jaar. Als + dit perceel wel in gebruik + was, werk dan de startdatum + bij in de perceelsinstelling.

@@ -157,7 +163,9 @@ export default function FarmBalanceNitrogenFieldBlock() { - + @@ -171,59 +179,76 @@ export default function FarmBalanceNitrogenFieldBlock() { - Helaas is het niet mogelijk om je balans uit te - rekenen + Helaas is het niet mogelijk om + je balans uit te rekenen {!errorMessage ? (

- Er is een onbekende fout opgetreden. - Probeer opnieuw of neem contact op met - Ondersteuning. + Er is een onbekende fout + opgetreden. Probeer + opnieuw of neem contact + op met Ondersteuning.

) : errorMessage.match( - /Missing required soil parameters/, - ) ? ( + /Missing required soil parameters/, + ) ? (

- Voor niet alle percelen zijn de - benodigde bodemparameters bekend: + Voor niet alle percelen + zijn de benodigde + bodemparameters bekend:


    - {errorMessage.match(/a_n_rt/) ? ( -
  • Totaal stikstofgehalte
  • + {errorMessage.match( + /a_n_rt/, + ) ? ( +
  • + Totaal + stikstofgehalte +
  • ) : null} {errorMessage.match( /b_soiltype_agr/, ) ? ( -
  • Agrarisch bodemtype
  • +
  • + Agrarisch + bodemtype +
  • ) : null} {errorMessage.match( /a_c_of|a_som_loi/, ) ? ( -
  • Organische stofgehalte
  • +
  • + Organische + stofgehalte +
  • ) : null}
) : (

- Er is helaas wat misgegaan. Probeer - opnieuw of neem contact op met - Ondersteuning en deel de volgende + Er is helaas wat + misgegaan. Probeer + opnieuw of neem contact + op met Ondersteuning en + deel de volgende foutmelding:

                                                             {JSON.stringify(
                                                                 {
-                                                                    message: errorMessage,
+                                                                    message:
+                                                                        errorMessage,
                                                                     page: page,
-                                                                    timestamp: new Date(),
+                                                                    timestamp:
+                                                                        new Date(),
                                                                 },
                                                                 null,
                                                                 2,
@@ -321,26 +346,24 @@ export default function FarmBalanceNitrogenFieldBlock() {
                                         
                                             Balans
                                             
-                                                De stikstofbalans voor {field.b_name} van{" "}
-                                                {farm.b_name_farm}. De balans is het
-                                                verschil tussen de totale aanvoer, afvoer en
-                                                emissie van stikstof. Een positieve balans
-                                                betekent een overschot aan stikstof, een
-                                                negatieve balans een tekort.
+                                                De stikstofbalans voor{" "}
+                                                {field.b_name} van{" "}
+                                                {farm.b_name_farm}. De balans is
+                                                het verschil tussen de totale
+                                                aanvoer, afvoer en emissie van
+                                                stikstof. Een positieve balans
+                                                betekent een overschot aan
+                                                stikstof, een negatieve balans
+                                                een tekort.