Skip to content

Add bulk soil analysis PDF upload with field matching#449

Merged
SvenVw merged 31 commits into
developmentfrom
FDM439
Feb 10, 2026
Merged

Add bulk soil analysis PDF upload with field matching#449
SvenVw merged 31 commits into
developmentfrom
FDM439

Conversation

@SvenVw
Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw commented Feb 3, 2026

Summary by CodeRabbit

  • New Features

    • Two-step bulk soil analysis upload: upload multiple PDF analyses, review/match to fields, and save.
    • Dashboard and sidebar shortcuts to start bulk uploads; per-file client/server PDF validation (5 MB limit, magic-byte checks).
  • Bug Fixes

    • Soil analysis lists now include analyses from before the selected year.
    • Prevent saving analyses with invalid depth values (added runtime NaN checks).
  • Documentation

    • Added changeset/release notes entries.

Closes #439

@SvenVw SvenVw self-assigned this Feb 3, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 3, 2026

🦋 Changeset detected

Latest commit: f2a42fd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@svenvw/fdm-app Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a two-step bulk soil analysis upload and review flow: multi-PDF Dropzone upload, server-side bulk PDF validation and extraction, automatic field matching (geometry/name), review table to map analyses to fields, and persistence endpoints; plus dashboard/sidebar entry points and changeset entries.

Changes

Cohort / File(s) Summary
Changesets
\.changeset/good-rooms-show.md, \.changeset/quick-clocks-nail.md
Added two changeset entries describing UI notes and the new bulk soil-analysis feature.
Dashboard & Sidebar UI
fdm-app/app/routes/farm.$b_id_farm._index.tsx, fdm-app/app/components/blocks/fields-new/sidebar.tsx
Added bulk-upload action cards/links/icons and a farm area summation guard; new navigation entry for bulk upload in both dashboard and wizard sidebar.
Bulk Upload Components
fdm-app/app/components/blocks/soil/bulk-upload-form.tsx, fdm-app/app/components/blocks/soil/bulk-upload-review.tsx
New React components: multi-file Dropzone upload form (client staging, per-file 5MB limit) and review table with ProcessedAnalysis type, per-analysis field Select, save/cancel flows and client-side validation.
NMI Integration
fdm-app/app/integrations/nmi.ts
Added PDF magic-byte validation and extractBulkSoilAnalyses(formData) to validate multiple PDFs, call NMI bulk endpoint, parse responses, and map each file to a soilAnalysis object (dates, parameters, location, depth handling, robust error handling).
Bulk Upload Routes (Farm & Wizard)
fdm-app/app/routes/farm.$b_id_farm.soil-analysis.bulk.tsx, fdm-app/app/routes/farm.create.$b_id_farm.$calendar.soil-analysis.bulk.tsx
Two new Remix routes with loaders/actions and page components implementing upload → review workflow, server-side extraction, auto-matching (polygon/name), and final save that persists matched analyses.
Soil Analysis Loader Change
fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.soil._index.tsx
Adjusted loader to call getSoilAnalyses({ start: null, end: timeframe.end }), removing the start bound while preserving the end bound.
Core Validation
fdm-core/src/soil.ts
Added NaN checks for a_depth_lower and a_depth_upper in addSoilAnalysis to throw descriptive errors when depths are invalid.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as "Bulk Upload UI"
    participant Route as "Bulk Upload Route"
    participant NMI as "NMI Integration"
    participant DB as "Database"

    User->>UI: Drag & drop multiple PDFs
    UI->>UI: Validate & stage files (client, 5MB/file)
    User->>UI: Submit files
    UI->>Route: POST FormData (files)
    Route->>NMI: extractBulkSoilAnalyses(formData)
    NMI->>NMI: Validate PDFs, extract analyses per file
    NMI-->>Route: Return processed analyses array
    Route-->>UI: Respond with analyses data
    UI->>UI: Auto-match analyses to fields (geometry/name)
    User->>UI: Inspect/adjust matches in Review UI
    UI->>Route: POST matches + analysesData (save)
    Route->>DB: addSoilAnalysis(...) per matched item
    DB-->>Route: Persisted
    Route-->>User: Redirect / success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • BoraIneviNMI
  • gerardhros

Poem

"I hop in the Dropzone with a joyful little thump,
PDFs like carrots — a crunchy, eager lump.
I sniff names and maps, then nudge each into place,
You click save — I tuck data into every field space. 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding bulk soil analysis PDF upload functionality with automatic field matching.
Linked Issues check ✅ Passed All coding requirements from issue #439 are implemented: bulk upload page, multi-file dropzone, NMI API integration, geometry/name matching, review table with parameters and field selector, and save/link persistence.
Out of Scope Changes check ✅ Passed All changes align with issue #439 requirements. Minor scope includes: entry points on Farm Dashboard and field sidebar, NMI validation improvements, depth validation in core, and changesets documenting the feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch FDM439

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SvenVw SvenVw marked this pull request as ready for review February 3, 2026 16:22
@coderabbitai coderabbitai Bot changed the title @coderabbitai Add bulk soil analysis PDF upload with field matching Feb 3, 2026
@coderabbitai coderabbitai Bot added branch:development Issue only affecting development, not the main branch (yet) enhancement New feature or request fdm-app labels Feb 3, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@fdm-app/app/components/blocks/soil/bulk-upload-form.tsx`:
- Around line 74-103: The Dropzone currently advertises "max 5MB per bestand"
but doesn't enforce it; add a maxSize prop to the Dropzone component (alongside
the existing name="soilAnalysisFiles", accept, multiple, value={files},
onFilesChange={handleFilesChange}) set to 5 * 1024 * 1024 and update
handleFilesChange to handle the Dropzone's file-rejection behavior so users see
immediate errors; also ensure the same 5MB limit is enforced in the
corresponding client-side Zod schema (e.g., form-upload.tsx) and the server
upload handler so UI and server validation match.
- Around line 53-59: The helper function formatFileSize uses the global
parseFloat which violates the Biome useNumberNamespace rule; update
formatFileSize to call Number.parseFloat instead of parseFloat (leave all other
logic intact) so the function uses Number.parseFloat((bytes / Math.pow(k,
i)).toFixed(2)) + " " + sizes[i]; ensure the symbol name formatFileSize is
updated in-place and no other behavior changes.
- Around line 38-44: The useEffect currently uses isUploading but doesn't
include it in the dependency array; update the effect declared with
useEffect(...) so its dependency list includes isUploading and onSuccess and
fetcher.data (remove fetcher.state since it's only used to derive isUploading)
to satisfy exhaustive-deps and ensure the effect reruns correctly when upload
state changes.

In `@fdm-app/app/components/blocks/soil/bulk-upload-review.tsx`:
- Around line 99-101: The Badge rendering currently checks row.original.a_p_al
but displays row.original.a_p_cc, causing "P-CaCl2: undefined" when a_p_cc is
missing; change the conditional to check for row.original.a_p_cc (or a
null/undefined-safe check like != null) before rendering the Badge so the
displayed value always exists; update the JSX where the Badge is created (the
Badge component line in bulk-upload-review.tsx that references a_p_al and
a_p_cc) to use row.original.a_p_cc in the condition.

In `@fdm-app/app/integrations/nmi.ts`:
- Around line 313-319: The bulk-path code handling field.b_depth assigns
Number(...) directly and can store NaN; update the logic around field.b_depth so
it first validates the "X-Y" format and that both parsed parts are finite
numbers (e.g., use isFinite after Number conversion or Number.isFinite) and only
then set soilAnalysis.a_depth_upper and soilAnalysis.a_depth_lower; if
validation fails, do not assign those properties (or set them to undefined/null)
to match the single-file flow. Ensure you touch the block referencing
field.b_depth and the soilAnalysis.a_depth_upper / soilAnalysis.a_depth_lower
assignments so invalid input cannot be persisted.

In `@fdm-app/app/routes/farm`.$b_id_farm._index.tsx:
- Around line 399-412: The JSX uses an unnecessary template literal for the
NavLink target; update the NavLink element (the JSX using NavLink with
to={`soil-analysis/bulk`}) to use a plain string literal instead
(to="soil-analysis/bulk") to satisfy the noUnusedTemplateLiteral rule and avoid
unneeded interpolation.

In `@fdm-app/app/routes/farm`.$b_id_farm.soil-analysis.bulk.tsx:
- Around line 75-95: The code maps over matches and calls addSoilAnalysis using
Number(analysis.a_depth_lower) / Number(analysis.a_depth_upper) which yields NaN
for missing/non-numeric depths and bypasses addSoilAnalysis validation; before
invoking Promise.all (in the matches.map block) validate that
analysis.a_depth_lower and analysis.a_depth_upper are present and are finite
numbers (e.g., Number.isFinite(Number(...))) and either skip/reject the
offending analysis (or return a rejected Promise) with a clear error so
addSoilAnalysis never receives NaN; reference the analysesData lookup, the
matches.map callback, and the a_depth_lower/a_depth_upper values when
implementing this pre-check.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.soil-analysis.bulk.tsx:
- Line 51: Remove the unnecessary await when calling the synchronous function
getCalendar in the bulk soil-analysis route: change the usage that assigns to
calendar from "await getCalendar(params)" to a plain synchronous call
"getCalendar(params)"; update any related variable assignment in that scope
(calendar) to reflect the synchronous return type so no Promise handling is used
and ensure consistency with the loader which already calls getCalendar without
await.
🧹 Nitpick comments (5)
fdm-app/app/components/blocks/soil/bulk-upload-review.tsx (1)

18-34: Consider exporting the Field type for reuse.

The ProcessedAnalysis type is exported but Field type is not. Since both route files use the same field structure, exporting Field would improve type consistency across consumers.

-type Field = {
+export type Field = {
     b_id: string
     b_name: string
 }
fdm-app/app/routes/farm.$b_id_farm.soil-analysis.bulk.tsx (2)

112-112: Unused import: navigate is declared but never used.

The navigate variable from useNavigate() is not used in this component.

♻️ Proposed fix
-    const navigate = useNavigate()

And update the import:

-import { data, type LoaderFunctionArgs, type ActionFunctionArgs, useLoaderData, useNavigate, useNavigation, useSubmit } from "react-router"
+import { data, type LoaderFunctionArgs, type ActionFunctionArgs, useLoaderData, useNavigation, useSubmit } from "react-router"

118-151: Consider extracting matching logic to a shared utility.

The handleUploadSuccess function with geometry and name matching is duplicated verbatim in farm.create.$b_id_farm.$calendar.soil-analysis.bulk.tsx. Extracting this to a shared utility (e.g., ~/lib/soil-analysis-matching.ts) would improve maintainability.

fdm-app/app/routes/farm.create.$b_id_farm.$calendar.soil-analysis.bulk.tsx (2)

102-102: Unused import: navigate is declared but never used.

Same issue as in the other bulk route file.

♻️ Proposed fix
-    const navigate = useNavigate()

And update the import:

-import { data, type LoaderFunctionArgs, type ActionFunctionArgs, useLoaderData, useNavigate, useNavigation, useSubmit } from "react-router"
+import { data, type LoaderFunctionArgs, type ActionFunctionArgs, useLoaderData, useNavigation, useSubmit } from "react-router"

108-139: Duplicated matching logic should be consolidated.

This handleUploadSuccess implementation is identical to the one in farm.$b_id_farm.soil-analysis.bulk.tsx. Consider extracting to a shared utility function.

Comment thread fdm-app/app/components/blocks/soil/bulk-upload-form.tsx Outdated
Comment thread fdm-app/app/components/blocks/soil/bulk-upload-form.tsx
Comment thread fdm-app/app/components/blocks/soil/bulk-upload-form.tsx
Comment thread fdm-app/app/components/blocks/soil/bulk-upload-review.tsx Outdated
Comment thread fdm-app/app/integrations/nmi.ts
Comment thread fdm-app/app/routes/farm.$b_id_farm._index.tsx Outdated
Comment thread fdm-app/app/routes/farm.$b_id_farm.soil-analysis.bulk.tsx Outdated
Comment thread fdm-app/app/routes/farm.create.$b_id_farm.$calendar.soil-analysis.bulk.tsx Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 3, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.04%. Comparing base (2aa2a9d) to head (f2a42fd).
⚠️ Report is 32 commits behind head on development.

Files with missing lines Patch % Lines
fdm-core/src/soil.ts 50.00% 2 Missing ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##           development     #449      +/-   ##
===============================================
- Coverage        88.07%   88.04%   -0.04%     
===============================================
  Files               91       91              
  Lines             4621     4625       +4     
  Branches          1492     1494       +2     
===============================================
+ Hits              4070     4072       +2     
- Misses             551      553       +2     
Flag Coverage Δ
fdm-calculator 88.76% <ø> (ø)
fdm-core 86.69% <50.00%> (-0.08%) ⬇️
fdm-data 92.12% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@fdm-app/app/components/blocks/soil/bulk-upload-review.tsx`:
- Around line 193-197: The saved matches currently only filter empty strings but
still include selections equal to "none"; update the handleSave logic (function
handleSave, variable matches and the onSave call) to exclude entries where
fieldId is an empty string or exactly "none" (case-sensitive per the contract)
before mapping to { analysisId, fieldId } so onSave only receives real mappings.

In `@fdm-app/app/integrations/nmi.ts`:
- Around line 16-24: The validatePdfMagicBytes function currently only checks
PDF magic bytes but needs a server-side size guard; update validatePdfMagicBytes
to first verify file.size is <= 5 * 1024 * 1024 (5 MB) and throw a clear Error
(e.g., "invalid: Bestand \"<name>\" is te groot, limiet 5MB") if it exceeds that
limit before reading the ArrayBuffer, then proceed with the existing
fileTypeFromBuffer checks; reference the validatePdfMagicBytes function and the
File parameter to locate the change.

In `@fdm-app/app/routes/farm`.$b_id_farm.soil-analysis.bulk.tsx:
- Around line 214-243: The code currently constructs new
Date(analysis.b_sampling_date) without validation; replicate the a_depth_lower
validation: parse and check analysis.b_sampling_date (e.g., const samplingDate =
new Date(analysis.b_sampling_date); if (!analysis.b_sampling_date ||
Number.isNaN(samplingDate.getTime())) { console.warn(...) ; return }) before
calling addSoilAnalysis so invalid or missing sampling dates are skipped; ensure
you use samplingDate when calling addSoilAnalysis (function name
addSoilAnalysis, variables analysis.b_sampling_date, analysesData, depthLower,
depthUpper).
🧹 Nitpick comments (1)
fdm-app/app/components/blocks/soil/bulk-upload-form.tsx (1)

200-205: Disable “Start Analyse” when no files are selected.

This avoids a no-op click path and makes the state clearer.

🔧 Suggested tweak
-                                <Button
+                                <Button
                                     onClick={handleUpload}
-                                    disabled={isUploading}
+                                    disabled={isUploading || files.length === 0}
                                     className="w-full lg:w-auto min-w-[140px]"
                                 >

Comment thread fdm-app/app/components/blocks/soil/bulk-upload-review.tsx Outdated
Comment thread fdm-app/app/integrations/nmi.ts
Comment thread fdm-app/app/routes/farm.$b_id_farm.soil-analysis.bulk.tsx
@coderabbitai coderabbitai Bot added the fdm-core label Feb 4, 2026
@SvenVw SvenVw requested a review from BoraIneviNMI February 4, 2026 09:24
@BoraIneviNMI
Copy link
Copy Markdown
Collaborator

It seems I can still try to couple an invalid PDF, which brings me to the Opslaan & Koppelen stage. Then I get an internal server error

Error (code: XJJV-UMN9): BaseError: Exception for addSoilAnalysis
...
context: {
    a_date: null,
    a_source: 'other',
    b_id: 'Twm8DKcmpDzH6pwr',
    a_depth_upper: NaN,
    a_depth_lower: NaN,
    b_sampling_date: Invalid Date
  },
  [cause]: RangeError: Invalid time value
  ...

@SvenVw
Copy link
Copy Markdown
Collaborator Author

SvenVw commented Feb 5, 2026

It seems I can still try to couple an invalid PDF, which brings me to the Opslaan & Koppelen stage. Then I get an internal server error

Error (code: XJJV-UMN9): BaseError: Exception for addSoilAnalysis
...
context: {
    a_date: null,
    a_source: 'other',
    b_id: 'Twm8DKcmpDzH6pwr',
    a_depth_upper: NaN,
    a_depth_lower: NaN,
    b_sampling_date: Invalid Date
  },
  [cause]: RangeError: Invalid time value
  ...

Thanks, I resolved it in 6745a11

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@fdm-app/app/components/blocks/soil/bulk-upload-review.tsx`:
- Around line 105-112: The cell renderer for accessorKey "b_sampling_date" calls
format(new Date(...)) without validating the date, which can throw RangeError;
update the cell function in bulk-upload-review.tsx to construct the Date once
(e.g., const d = new Date(row.original.b_sampling_date)), check validity (using
date-fns isValid(d) or d instanceof Date && !isNaN(d.getTime())), and only call
format(d, "P", { locale: nl }) when valid; otherwise return "-" to safely guard
against invalid or malformed dates.
🧹 Nitpick comments (2)
fdm-app/app/components/blocks/soil/bulk-upload-review.tsx (1)

69-71: Prefer undefined instead of "" for unselected field values.

This avoids empty-string selections and aligns with the Select component’s expected “no value” state; it also simplifies filtering.

♻️ Proposed refactor
-    const [matches, setMatches] = useState<Record<string, string>>(
-        Object.fromEntries(analyses.map((a) => [a.id, a.matchedFieldId || ""])),
-    )
+    const [matches, setMatches] = useState<Record<string, string | undefined>>(
+        Object.fromEntries(
+            analyses.map((a) => [a.id, a.matchedFieldId || undefined]),
+        ),
+    )
@@
-                <Select
-                    value={matches[row.original.id]}
+                <Select
+                    value={matches[row.original.id]}
                     onValueChange={(value) =>
                         handleFieldChange(row.original.id, value)
                     }
                 >
@@
-            .filter(([analysisId, fieldId]) => {
-                if (fieldId === "" || fieldId === "none") return false
+            .filter(([analysisId, fieldId]) => {
+                if (!fieldId || fieldId === "none") return false
Based on learnings: In fdm-app Select components, using `undefined` instead of empty string (`""`) as fallback value prevents empty strings from being submitted as form values.

Also applies to: 146-163, 206-215

fdm-app/app/routes/farm.create.$b_id_farm.$calendar.soil-analysis.bulk.tsx (1)

82-130: Consider extracting shared bulk-save helpers to reduce drift.

The matching and validation logic is duplicated across the two bulk routes; a shared utility would reduce maintenance risk if the logic evolves.

Also applies to: 198-253

Comment thread fdm-app/app/components/blocks/soil/bulk-upload-review.tsx Outdated
@SvenVw SvenVw requested review from gerardhros and removed request for BoraIneviNMI February 6, 2026 14:18
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Sven Verweij <37927107+SvenVw@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@fdm-app/app/components/blocks/soil/bulk-upload-review.tsx`:
- Around line 120-139: The badges in bulk-upload-review.tsx (e.g., checks around
row.original.a_som_loi, a_p_al, a_p_cc, a_nmin_cc) only guard against undefined
and will render literal "null" if the API returns null; change the presence
checks to use loose null checks (e.g., value != null) for each of those fields
so both null and undefined are treated as absent, and keep the existing display
formatting (including the "%" suffix for a_som_loi) only when the value is
present.
🧹 Nitpick comments (3)
fdm-app/app/components/blocks/soil/bulk-upload-review.tsx (3)

48-48: Consider narrowing the data type from any.

data: any in an exported type leaks an untyped surface to consumers. Using Record<string, unknown> would enforce explicit type narrowing at usage sites and prevent accidental unsafe access.


167-198: Extract the repeated date-validation logic into a helper.

The same b_sampling_date && !Number.isNaN(new Date(...).getTime()) pattern appears in the date column cell (Line 108), the status column cell (Line 173), handleSave (Line 212), the table-body row styling (Line 254), and the disabled-button predicate (Line 324). A single helper eliminates the duplication and the repeated new Date allocations.

♻️ Proposed helper
+const isValidDate = (dateStr: string | undefined | null): boolean => {
+    if (!dateStr) return false
+    const d = new Date(dateStr)
+    return !Number.isNaN(d.getTime())
+}

Then replace each inline check, e.g.:

-                const isValid =
-                    row.original.b_sampling_date &&
-                    !Number.isNaN(new Date(row.original.b_sampling_date).getTime())
+                const isValid = isValidDate(row.original.b_sampling_date)

317-328: The disabled predicate duplicates the handleSave filter logic.

Lines 320-327 reimplement the same filter as handleSave (Lines 208-216). If eligibility criteria change, both must stay in sync. Consider extracting a shared derived value.

♻️ Proposed approach

Compute the valid matches once (e.g., via useMemo) and reuse in both places:

+    const validMatches = useMemo(
+        () =>
+            Object.entries(matches)
+                .filter(([analysisId, fieldId]) => {
+                    if (fieldId === "" || fieldId === "none") return false
+                    const analysis = analyses.find((a) => a.id === analysisId)
+                    return isValidDate(analysis?.b_sampling_date)
+                })
+                .map(([analysisId, fieldId]) => ({ analysisId, fieldId })),
+        [matches, analyses],
+    )
+
     const handleSave = () => {
-        const result = Object.entries(matches)
-            .filter(([analysisId, fieldId]) => { ... })
-            .map(...)
-        onSave(result)
+        onSave(validMatches)
     }

Then in the button:

     <Button
         onClick={handleSave}
-        disabled={
-            !Object.entries(matches).some(([analysisId, fieldId]) => { ... })
-        }
+        disabled={validMatches.length === 0}
     >

Comment thread fdm-app/app/components/blocks/soil/bulk-upload-review.tsx Outdated
@SvenVw SvenVw merged commit 27d72d6 into development Feb 10, 2026
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

branch:development Issue only affecting development, not the main branch (yet) enhancement New feature or request fdm-app fdm-core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Bulk Soil Analysis Upload

2 participants