Skip to content
5 changes: 5 additions & 0 deletions .changeset/giant-files-yell.md
Original file line number Diff line number Diff line change
@@ -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
77 changes: 77 additions & 0 deletions fdm-app/app/components/blocks/balance/skeletons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card"
import { Skeleton } from "~/components/ui/skeleton"

export function NitrogenBalanceCardSkeleton() {
return (
<Card className="animate-pulse">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="h-4 w-24 bg-muted rounded" />
<Skeleton className="h-4 w-4 rounded-full" />
</CardHeader>
<CardContent>
<div className="h-8 w-32 bg-muted rounded" />
<p className="h-3 w-20 bg-muted rounded mt-2" />
</CardContent>
</Card>
)
}

export function NitrogenBalanceChartSkeleton() {
return (
<Card className="col-span-4 animate-pulse">
<CardHeader>
<CardTitle className="h-6 w-32 bg-muted rounded" />
<CardDescription className="h-4 w-full bg-muted rounded mt-2" />
<CardDescription className="h-4 w-full bg-muted rounded mt-1" />
<CardDescription className="h-4 w-full bg-muted rounded mt-1" />
</CardHeader>
<CardContent className="pl-2">
<Skeleton className="h-64 w-full" />
</CardContent>
</Card>
)
}

export function NitrogenBalanceFieldsSkeleton() {
return (
<Card className="col-span-3 animate-pulse">
<CardHeader>
<CardTitle className="h-6 w-32 bg-muted rounded" />
<CardDescription className="h-4 w-full bg-muted rounded mt-2" />
</CardHeader>
<CardContent>
<div className="space-y-8">
{[...Array(4)].map((_, i) => (
<div key={`field-balance-skeleton-${i}`} className="flex items-center">
<Skeleton className="h-6 w-6 rounded-full" />
<div className="ml-4 space-y-1">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-3 w-[150px]" />
</div>
<div className="ml-auto">
<Skeleton className="h-4 w-[50px]" />
</div>
</div>
))}
</div>
</CardContent>
</Card>
)
}

export function NitrogenBalanceFallback() {
return (
<div className="space-y-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<NitrogenBalanceCardSkeleton />
<NitrogenBalanceCardSkeleton />
<NitrogenBalanceCardSkeleton />
<NitrogenBalanceCardSkeleton />
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<NitrogenBalanceChartSkeleton />
<NitrogenBalanceFieldsSkeleton />
</div>
</div>
)
}
2 changes: 1 addition & 1 deletion fdm-app/app/components/blocks/norms/field-norms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function FieldNorms({ fieldNorms, fieldOptions }: FieldNormsProps) {

return (
<div>
<h2 className="text-2xl font-semibold mb-6">Perceelniveau</h2>
<h2 className="text-2xl font-semibold mb-6">Perceelsniveau</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{fieldNorms.map((field) => (
<Card
Expand Down
95 changes: 95 additions & 0 deletions fdm-app/app/components/blocks/norms/skeletons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "~/components/ui/card"
import { Separator } from "~/components/ui/separator"

export function FarmNormsSkeleton() {
return (
<div className="mb-0 animate-pulse">
<h2 className="h-8 w-48 bg-muted rounded mb-4" aria-label="Loading farm norms" />
<div className="grid gap-4 xl:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="h-4 w-24 bg-muted rounded" />
</CardHeader>
<CardContent>
<div className="h-8 w-32 bg-muted rounded" />
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="h-4 w-24 bg-muted rounded" />
</CardHeader>
<CardContent>
<div className="h-8 w-32 bg-muted rounded" />
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="h-4 w-48 bg-muted rounded" />
</CardHeader>
<CardContent>
<div className="h-8 w-32 bg-muted rounded" />
</CardContent>
</Card>
</div>
</div>
)
}

export function FieldNormsSkeleton() {
return (
<div className="animate-pulse">
<h2 className="h-8 w-48 bg-muted rounded mb-6" aria-label="Loading field norms" />
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
{[...Array(3)].map((_, i) => (
<Card key={`field-norm-skeleton-${i}`} className="hover:shadow-md transition-shadow border-gray-200">
<CardHeader>
<div>
<CardTitle className="h-6 w-3/4 bg-muted rounded" />
<CardDescription className="h-4 w-1/2 bg-muted rounded mt-2" />
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex justify-between items-center p-3 bg-gray-50 rounded-lg border border-gray-100">
<div>
<p className="h-4 w-24 bg-muted rounded" />
<p className="h-3 w-32 bg-muted rounded mt-1" />
</div>
<div className="text-right">
<p className="h-6 w-24 bg-muted rounded" />
</div>
</div>
<div className="flex justify-between items-center p-3 bg-gray-50 rounded-lg border border-gray-100">
<div>
<p className="h-4 w-24 bg-muted rounded" />
<p className="h-3 w-32 bg-muted rounded mt-1" />
</div>
<div className="text-right">
<p className="h-6 w-24 bg-muted rounded" />
</div>
</div>
<div className="flex justify-between items-center p-3 bg-gray-50 rounded-lg border border-gray-100">
<div>
<p className="h-4 w-48 bg-muted rounded" />
<p className="h-3 w-32 bg-muted rounded mt-1" />
</div>
<div className="text-right">
<p className="h-6 w-24 bg-muted rounded" />
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
)
}

export function NormsFallback() {
return (
<div className="space-y-6 px-10 pb-16">
<FarmNormsSkeleton />
<Separator className="my-8" />
<FieldNormsSkeleton />
</div>
)
}
58 changes: 58 additions & 0 deletions fdm-app/app/components/blocks/nutrient-advice/skeletons.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Card className="relative animate-pulse">
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="h-10 w-10 bg-muted rounded-md" />
<CardTitle className="h-6 w-24 bg-muted rounded" />
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="text-center space-y-1">
<div className="h-10 w-32 mx-auto bg-muted rounded" />
<div className="h-4 w-20 mx-auto bg-muted rounded" />
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="h-4 w-24 bg-muted rounded" />
<span className="h-4 w-12 bg-muted rounded" />
</div>
<Progress value={0} className="h-3" />
</div>
<div className="space-y-2 mt-3">
<Separator />
<div className="space-y-3">
{[...Array(2)].map((_, i) => (
<div key={`skeleton-detail-${i}`} className="flex justify-between items-center p-2 bg-muted/50 rounded">
<div className="space-y-1">
<p className="h-4 w-32 bg-muted rounded" />
<p className="h-3 w-24 bg-muted rounded" />
</div>
<div className="text-right">
<p className="h-4 w-16 bg-muted rounded" />
<p className="h-3 w-12 bg-muted rounded" />
</div>
</div>
))}
</div>
</div>
</CardContent>
</Card>
)
}

export function NutrientAdviceFallback() {
return (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<NutrientCardSkeleton />
<NutrientCardSkeleton />
<NutrientCardSkeleton />
</div>
)
}
Loading