Skip to content

Add a Dropzone component for shapefile and soil analysis uploads#323

Merged
BoraIneviNMI merged 18 commits into
developmentfrom
FDM312-dropzone-component
Nov 10, 2025
Merged

Add a Dropzone component for shapefile and soil analysis uploads#323
BoraIneviNMI merged 18 commits into
developmentfrom
FDM312-dropzone-component

Conversation

@BoraIneviNMI
Copy link
Copy Markdown
Collaborator

@BoraIneviNMI BoraIneviNMI commented Nov 4, 2025

Enhancements

  • Remove clutter from form-upload components by moving the drag and drop handler into a new Dropzone component.

Summary by CodeRabbit

  • New Features

    • Adds a unified drag-and-drop Dropzone with keyboard support, merge/reset controls, and clearer file validation/status messages.
  • Refactor

    • Consolidates parcel and soil upload flows into the Dropzone-based flow, simplifying file selection, display, and form state handling.
  • Bug Fixes

    • More reliable detection of required shapefile parts and improved DBF parsing, success/error messaging, and upload feedback.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Nov 4, 2025

🦋 Changeset detected

Latest commit: 27e7cad

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 Patch

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 Nov 4, 2025

Walkthrough

Extracts drag-and-drop and file-selection logic into a new reusable Dropzone component, updates two form-upload components (mijnpercelen and soil) to use Dropzone and derive selected files from form state, and adds a changeset documenting the patch release. (34 words)

Changes

Cohort / File(s) Change Summary
New Dropzone component
fdm-app/app/components/custom/dropzone.tsx
Adds a client-side Dropzone component, DropzoneProps type and useDropzoneContext hook implementing drag-and-drop, hidden native input syncing via DataTransfer, extension/size validation, configurable mergeFiles strategy, keyboard activation, optional reset, and onFilesChange callback.
Mijnpercelen form upload
fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
Replaces manual file input and in-component drag/drop handlers with Dropzone; removes local file state; derives completeness from form shapefile state; sets validated files via form.setValue("shapefile", ...) and integrates DBF parsing into the updated flow.
Soil form upload
fdm-app/app/components/blocks/soil/form-upload.tsx
Replaces manual file input and drag/drop logic with Dropzone; removes local fileName and handlers; derives selected file from form watch; wires onFilesChange to update form values; preserves file-type/size validation messages.
Changeset
.changeset/bright-pillows-wink.md
Adds a patch release note documenting the Dropzone refactor for @svenvw/fdm-app.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant Dropzone
  participant Form as "React Hook Form"
  participant DBFParser

  Note over User,Dropzone: User selects files via drag/drop or file picker
  User->>Dropzone: drag/drop or choose file(s)
  Dropzone->>Dropzone: validate extensions & sizes
  alt validation OK
    Dropzone->>Form: onFilesChange(files) -> form.setValue(...)
    Form->>DBFParser: trigger DBF parse if .dbf present
    DBFParser-->>Form: parsed metadata
    Form-->>User: update selectedFiles / status
  else validation error
    Dropzone-->>User: show validation error notification
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review focus:
    • fdm-app/app/components/custom/dropzone.tsx — DataTransfer syncing, mergeFiles behavior, validation and keyboard accessibility.
    • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx — correctness of form.setValue("shapefile", ...) and DBF parse trigger placement.
    • fdm-app/app/components/blocks/soil/form-upload.tsx — single vs multiple file handling and form blur/validation interactions.

Possibly related PRs

Suggested reviewers

  • gerardhros

Poem

🐇 I hop to the Dropzone, tidy and light,
Files gather neatly, no more fight.
I validate, merge, and pass them along,
Parsers hum softly — a clean upload song.
Hooray for neat code, all day and night.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a new Dropzone component that is used in shapefile and soil analysis upload components.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch FDM312-dropzone-component

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 4, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.68%. Comparing base (592e154) to head (27e7cad).
⚠️ Report is 19 commits behind head on development.

Additional details and impacted files
@@             Coverage Diff              @@
##           development     #323   +/-   ##
============================================
  Coverage        87.68%   87.68%           
============================================
  Files               69       69           
  Lines             3499     3499           
  Branches           973      973           
============================================
  Hits              3068     3068           
  Misses             431      431           
Flag Coverage Δ
fdm-calculator 88.10% <ø> (ø)
fdm-core 86.86% <ø> (ø)
fdm-data 93.42% <ø> (ø)

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.

@coderabbitai coderabbitai Bot added branch:development Issue only affecting development, not the main branch (yet) enhancement New feature or request fdm-app labels Nov 4, 2025
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: 0

🧹 Nitpick comments (3)
.changeset/bright-pillows-wink.md (1)

5-5: Use hyphens for compound modifiers.

The phrase "drag and drop handlers" should use a hyphenated compound modifier: "drag-and-drop handlers".

Apply this diff:

-Removed clutter from form-upload components by moving the drag and drop handlers into a new Dropzone component.
+Removed clutter from form-upload components by moving the drag-and-drop handlers into a new Dropzone component.
fdm-app/app/components/blocks/soil/form-upload.tsx (1)

78-78: Consider the stability of form.reset in the dependency array.

While including form.reset in the dependency array is a common pattern with react-hook-form (since the form object is stable), it can be confusing. Consider using the ESLint disable comment to clarify intent:

-    }, [isSubmitting, actionData, form.reset])
+    }, [isSubmitting, actionData, form.reset]) // eslint-disable-line react-hooks/exhaustive-deps

Or better yet, ensure form.reset is stable by not including it if the form instance doesn't change.

fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (1)

339-348: Use selectedFiles instead of form.getValues to avoid redundant reads.

The selectedFiles variable already watches the "shapefile" form value (line 114), so calling form.getValues("shapefile") here is redundant and could impact performance.

Apply this diff:

 <RequiredFilesStatus
     files={
-        form.getValues(
-            "shapefile",
-        ) || []
+        selectedFiles
     }
     requiredExtensions={
         requiredExtensions
     }
 />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1c842f and 6e72706.

⛔ Files ignored due to path filters (1)
  • fdm-app/app/components/ui/dropzone.tsx is excluded by !fdm-app/app/components/ui/**
📒 Files selected for processing (3)
  • .changeset/bright-pillows-wink.md (1 hunks)
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (4 hunks)
  • fdm-app/app/components/blocks/soil/form-upload.tsx (7 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2024-11-25T12:42:32.783Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 6
File: fdm-app/vite.config.ts:5-9
Timestamp: 2024-11-25T12:42:32.783Z
Learning: In the `fdm-app` project, SvenVw is preparing for migration to Remix v3 and may include type declarations or configurations for v3 features in advance, such as in `vite.config.ts`.

Applied to files:

  • .changeset/bright-pillows-wink.md
📚 Learning: 2025-09-26T08:34:50.413Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 279
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.norms.tsx:277-283
Timestamp: 2025-09-26T08:34:50.413Z
Learning: In the fdm project, fdm-core and fdm-app are updated together as part of a monorepo structure, which eliminates legacy data concerns when new fields like b_isproductive are introduced. Both packages are synchronized, so there's no need for defensive coding against undefined values for newly introduced database fields.

Applied to files:

  • .changeset/bright-pillows-wink.md
📚 Learning: 2025-08-13T11:05:40.105Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 237
File: fdm-data/src/cultivations/catalogues/brp.ts:65-68
Timestamp: 2025-08-13T11:05:40.105Z
Learning: In fdm-app Select components, using `undefined` instead of empty string (`""`) as fallback value prevents empty strings from being submitted as form values. This approach fixes the issue at the UI source rather than requiring backend validation.

Applied to files:

  • .changeset/bright-pillows-wink.md
📚 Learning: 2025-05-09T14:53:44.578Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:53:44.578Z
Learning: In the context of this React Router v7 project, it's important to follow the pattern of importing only the types (like UseFormReturn) from "react-hook-form" while importing the Form component from "react-router" to avoid naming conflicts.

Applied to files:

  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2025-05-09T14:58:10.465Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:58:10.465Z
Learning: When updating React components that use both react-hook-form and React Router v7, it's important to only import types (like UseFormReturn, FieldValues) from react-hook-form to avoid naming conflicts with React Router's Form component. Use `import type { ... } from 'react-hook-form'` syntax to ensure only types are imported.

Applied to files:

  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2025-05-09T14:41:43.484Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/fertilizer-applications/form.tsx:6-6
Timestamp: 2025-05-09T14:41:43.484Z
Learning: The project uses React Router v7 which exports a Form component directly from the "react-router" package, making importing from "remix-run/react" unnecessary.

Applied to files:

  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2025-05-09T14:41:43.484Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/fertilizer-applications/form.tsx:6-6
Timestamp: 2025-05-09T14:41:43.484Z
Learning: The project uses React Router v7 which exports a Form component directly from the "react-router" package, not from "remix-run/react".

Applied to files:

  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2025-04-18T13:49:17.029Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 124
File: fdm-app/app/components/custom/farm/farm-title.tsx:3-3
Timestamp: 2025-04-18T13:49:17.029Z
Learning: In the fdm project, NavLink and other routing components can be imported from either "react-router" or "react-router-dom" as react-router-dom is included in react-router.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2024-12-16T10:56:07.561Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 16
File: fdm-app/app/routes/app.addfarm.$b_id_farm.cultivations.$b_lu_catalogue.fertilizers.tsx:1-1
Timestamp: 2024-12-16T10:56:07.561Z
Learning: The project uses `react-router` v7, and the `data` function is exported and used for error handling in loaders and actions.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
🧬 Code graph analysis (2)
fdm-app/app/components/blocks/soil/form-upload.tsx (1)
fdm-app/app/components/ui/dropzone.tsx (1)
  • Dropzone (49-230)
fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (1)
fdm-app/app/components/ui/dropzone.tsx (1)
  • Dropzone (49-230)
🪛 LanguageTool
.changeset/bright-pillows-wink.md

[grammar] ~5-~5: Use a hyphen to join words.
Context: ...orm-upload components by moving the drag and drop handlers into a new Dropzone co...

(QB_NEW_EN_HYPHEN)


[grammar] ~5-~5: Use a hyphen to join words.
Context: ...upload components by moving the drag and drop handlers into a new Dropzone compon...

(QB_NEW_EN_HYPHEN)

🔇 Additional comments (10)
fdm-app/app/components/blocks/soil/form-upload.tsx (4)

3-3: LGTM: Import additions are correct.

The useMemo and Dropzone imports are properly added and used in the component.

Also applies to: 25-25


41-45: LGTM: Derived state implementation is correct.

The selectedFile watch and memoized dropzoneValue array correctly track and transform the form state for the Dropzone component.


80-83: LGTM: File change handler correctly extracts single file.

The handleFilesChange function correctly takes an array (as required by Dropzone's onFilesChange callback) and extracts the first file for this single-file upload scenario.


113-194: LGTM: Dropzone integration is well-implemented.

The Dropzone component is properly configured with all necessary props, and the UI feedback for different upload states is clear and user-friendly.

fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (6)

32-32: LGTM: Dropzone import added correctly.


114-121: LGTM: Derived state implementation is efficient.

The selectedFiles watch and derived hasAllRequiredFiles boolean correctly track shapefile completeness from form state.


129-150: LGTM: File handling with DBF parsing is well-implemented.

The handleFilesSet function correctly:

  • Updates form state with the new files
  • Resets upload state
  • Parses the DBF file to extract field names
  • Handles errors gracefully with user feedback

284-316: LGTM: Dropzone configuration is comprehensive and correct.

The Dropzone is properly configured for multiple shapefile uploads with:

  • Correct props (value, accept, multiple, onFilesChange)
  • Custom mergeFiles logic for intelligent shapefile part merging
  • Conditional styling based on upload state and file completeness

317-372: LGTM: Upload status UI provides clear user feedback.

The UI correctly displays:

  • Selected file names
  • Required file status indicators
  • Success and error states with appropriate icons and messages

502-537: LGTM: Shapefile merge logic is well-designed.

The mergeShapefileParts function intelligently handles:

  • Complete replacement when all required files are provided at once
  • Incremental updates by extension when adding files one-by-one
  • Deduplication by filename

This approach provides good UX for shapefile uploads.

Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw left a comment

Choose a reason for hiding this comment

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

Thanks, very nice! Just some small comments

Comment thread .changeset/bright-pillows-wink.md Outdated
Comment thread fdm-app/app/components/custom/dropzone.tsx
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e72706 and 8fd5d37.

📒 Files selected for processing (4)
  • .changeset/bright-pillows-wink.md (1 hunks)
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (5 hunks)
  • fdm-app/app/components/blocks/soil/form-upload.tsx (6 hunks)
  • fdm-app/app/components/custom/dropzone.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .changeset/bright-pillows-wink.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-10T15:05:38.163Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 163
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:27-29
Timestamp: 2025-06-10T15:05:38.163Z
Learning: In the fdm-app codebase, the component directory structure is intentionally organized with `~/components/custom/` containing small reusable components that can be used in both blocks and pages, while `~/components/blocks/` contains larger block-level components. Not all components should be migrated from custom to blocks - the custom directory serves as a library of smaller utility components.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
🪛 Biome (2.1.2)
fdm-app/app/components/custom/dropzone.tsx

[error] 196-196: The HTML element label is non-interactive. Do not use tabIndex.

Adding non-interactive elements to the keyboard navigation flow can confuse users.
Unsafe fix: Remove the tabIndex attribute.

(lint/a11y/noNoninteractiveTabindex)

Comment thread fdm-app/app/components/custom/dropzone.tsx Outdated
Comment thread fdm-app/app/components/custom/dropzone.tsx
Comment thread fdm-app/app/components/custom/dropzone.tsx
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: 4

♻️ Duplicate comments (1)
fdm-app/app/components/custom/dropzone.tsx (1)

201-221: Critical accessibility violation and duplicate ref assignment.

This segment has two major issues that were flagged in previous reviews but remain unaddressed:

  1. Accessibility: Placing tabIndex={0} on a non-interactive <label> violates accessibility standards and confuses keyboard users.
  2. Duplicate ref: Line 208 assigns the forwarded ref to the <label>, overriding the correct assignment to the <input> at lines 188-191. This breaks integrations like react-hook-form that expect ref to point to the input element.

Replace the <label> with an interactive <div> and remove ref={ref} from it:

-                <label
+                <div
+                    role="button"
                     tabIndex={0}
                     className={cn(
                         "flex-col items-center justify-center w-full h-32 rounded-md border border-dashed border-muted-foreground/25 px-6 py-4 text-center transition-colors hover:bg-muted/25",
                         disabled ? "hidden" : "flex",
                         className,
                     )}
-                    ref={ref}
-                    htmlFor={labelId}
+                    onClick={() => inputRef.current?.click()}
                     aria-label="Upload shapefile files by clicking or dragging and dropping"
                     onKeyDown={(e) => {
                         if (e.key === "Enter" || e.key === " ") {
                             e.preventDefault()
                             inputRef.current?.click()
                         }
                     }}
                     onDragOver={handleDragOver}
                     onDrop={handleDrop}
                 >
                     {children}
-                </label>
+                </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd5d37 and 5ce0e36.

📒 Files selected for processing (1)
  • fdm-app/app/components/custom/dropzone.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-06-10T15:05:38.163Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 163
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:27-29
Timestamp: 2025-06-10T15:05:38.163Z
Learning: In the fdm-app codebase, the component directory structure is intentionally organized with `~/components/custom/` containing small reusable components that can be used in both blocks and pages, while `~/components/blocks/` contains larger block-level components. Not all components should be migrated from custom to blocks - the custom directory serves as a library of smaller utility components.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-13T11:05:40.105Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 237
File: fdm-data/src/cultivations/catalogues/brp.ts:65-68
Timestamp: 2025-08-13T11:05:40.105Z
Learning: In fdm-app Select components, using `undefined` instead of empty string (`""`) as fallback value prevents empty strings from being submitted as form values. This approach fixes the issue at the UI source rather than requiring backend validation.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-05-09T14:58:10.465Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:58:10.465Z
Learning: When updating React components that use both react-hook-form and React Router v7, it's important to only import types (like UseFormReturn, FieldValues) from react-hook-form to avoid naming conflicts with React Router's Form component. Use `import type { ... } from 'react-hook-form'` syntax to ensure only types are imported.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
🪛 Biome (2.1.2)
fdm-app/app/components/custom/dropzone.tsx

[error] 202-202: The HTML element label is non-interactive. Do not use tabIndex.

Adding non-interactive elements to the keyboard navigation flow can confuse users.
Unsafe fix: Remove the tabIndex attribute.

(lint/a11y/noNoninteractiveTabindex)

🔇 Additional comments (6)
fdm-app/app/components/custom/dropzone.tsx (6)

71-75: LGTM! Proper normalization of accept tokens.

The token normalization correctly handles case variations and whitespace, addressing the previous review feedback.


112-123: LGTM! Clean file validation with user feedback.

The validation correctly checks normalized extensions and provides non-blocking warnings via toast notifications, allowing valid files through while alerting users to invalid types.


180-200: LGTM! Proper ref forwarding to the input element.

The ref callback correctly merges the internal inputRef with the forwarded ref on the actual <input> element, ensuring both the component's internal logic and external consumers (e.g., react-hook-form) can access the input.


238-246: LGTM! Standard context hook pattern.

The hook correctly enforces usage within a Dropzone provider and provides clear error messages.


169-178: Verify the necessity of the Context.Provider key.

Using key={JSON.stringify(fileNames)} forces the entire context and its children to remount whenever files change. This is expensive and should only be used if you need to reset internal state in child components.

Typically, context consumers should reactively update when the value prop changes, making the key unnecessary.

If remounting is intentional, please confirm the use case. Otherwise, consider removing the key:

         <DropzoneContext.Provider
-            key={JSON.stringify(fileNames)}
             value={{
                 files,
                 accept: acceptedFileExtensions,

1-1: Component correctly placed in custom/ directory.

Per codebase conventions and previous feedback, this small reusable component is appropriately located in ~/components/custom/.

Based on learnings.

Comment thread fdm-app/app/components/custom/dropzone.tsx
Comment thread fdm-app/app/components/custom/dropzone.tsx Outdated
Comment thread fdm-app/app/components/custom/dropzone.tsx
Comment thread fdm-app/app/components/custom/dropzone.tsx
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: 2

🧹 Nitpick comments (2)
fdm-app/app/components/custom/dropzone.tsx (2)

171-171: Remove the unnecessary key prop on Context.Provider.

key={JSON.stringify(fileNames)} forces the Provider to unmount and remount whenever files change, destroying internal state in all consuming children. Context value changes already trigger re-renders in consumers without requiring a key.

Apply this diff:

         <DropzoneContext.Provider
-            key={JSON.stringify(fileNames)}
             value={{

126-167: Consider extracting shared file processing logic.

handleFileChange and handleDrop contain nearly identical logic (validation, merging, DataTransfer update). Extracting the common flow reduces duplication and improves maintainability.

Example refactor:

const processFiles = async (newFiles: File[]) => {
    const validNewFiles = newFiles.filter(checkNewFile)
    if (validNewFiles.length === 0) return

    const finalFiles = await handleFilesSet(files, validNewFiles)

    if (inputRef.current) {
        const container = new DataTransfer()
        finalFiles.forEach((f) => container.items.add(f))
        inputRef.current.files = container.files
    }
}

const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files) {
        await processFiles(Array.from(event.target.files))
    }
}

const handleDrop = async (e: React.DragEvent<HTMLLabelElement>) => {
    e.preventDefault()
    if (e.dataTransfer.files?.length > 0) {
        await processFiles(Array.from(e.dataTransfer.files))
        e.dataTransfer.clearData()
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ce0e36 and 678b9f5.

📒 Files selected for processing (1)
  • fdm-app/app/components/custom/dropzone.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-06-10T15:05:38.163Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 163
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:27-29
Timestamp: 2025-06-10T15:05:38.163Z
Learning: In the fdm-app codebase, the component directory structure is intentionally organized with `~/components/custom/` containing small reusable components that can be used in both blocks and pages, while `~/components/blocks/` contains larger block-level components. Not all components should be migrated from custom to blocks - the custom directory serves as a library of smaller utility components.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-13T11:05:40.105Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 237
File: fdm-data/src/cultivations/catalogues/brp.ts:65-68
Timestamp: 2025-08-13T11:05:40.105Z
Learning: In fdm-app Select components, using `undefined` instead of empty string (`""`) as fallback value prevents empty strings from being submitted as form values. This approach fixes the issue at the UI source rather than requiring backend validation.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-05-09T14:58:10.465Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:58:10.465Z
Learning: When updating React components that use both react-hook-form and React Router v7, it's important to only import types (like UseFormReturn, FieldValues) from react-hook-form to avoid naming conflicts with React Router's Form component. Use `import type { ... } from 'react-hook-form'` syntax to ensure only types are imported.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-02-13T09:03:11.890Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 71
File: fdm-app/app/routes/farm.create.$b_id_farm.cultivations.$b_lu_catalogue.crop.harvest._index.tsx:111-135
Timestamp: 2025-02-13T09:03:11.890Z
Learning: When adding multiple harvests in fdm-app, use Promise.all instead of Promise.allSettled to ensure atomic behavior - if one harvest addition fails, all should fail and rollback to maintain data consistency.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-11T12:24:32.200Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 233
File: fdm-app/app/components/blocks/atlas-fields/cultivation-history.tsx:53-53
Timestamp: 2025-08-11T12:24:32.200Z
Learning: In `fdm-app/app/components/blocks/atlas-fields/cultivation-history.tsx`, the NMI API for cultivations guarantees that each year will be unique in the cultivation history data, so using `cultivation.year` as a React list key is safe and won't cause duplicate key warnings.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
🪛 Biome (2.1.2)
fdm-app/app/components/custom/dropzone.tsx

[error] 203-203: The HTML element label is non-interactive. Do not use tabIndex.

Adding non-interactive elements to the keyboard navigation flow can confuse users.
Unsafe fix: Remove the tabIndex attribute.

(lint/a11y/noNoninteractiveTabindex)

🔇 Additional comments (5)
fdm-app/app/components/custom/dropzone.tsx (5)

22-25: LGTM—proper guard for files without extensions.

The function correctly handles filenames without dots by checking dotIndex === -1 before slicing.


72-76: LGTM—accept tokens normalized correctly.

Token normalization (trim + lowercase) prevents whitespace and case mismatches from causing false rejections.


83-86: LGTM—file deduplication by name using Map.

The Map-based approach correctly deduplicates files by filename, replacing earlier files with later ones of the same name.


90-94: LGTM—input cleared with empty string.

Correctly assigns "" instead of null to clear the input value.


189-192: LGTM—ref callback correctly merges internal and forwarded refs.

The callback ref pattern properly assigns both inputRef.current and the forwarded ref to the input element.

Comment on lines +68 to +70
const files =
value ??
(inputRef.current?.files ? Array.from(inputRef.current.files) : [])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the controlled/uncontrolled state mixing.

The files derivation reads directly from inputRef.current?.files when value is undefined. This is non-reactive—changes to the input's FileList won't trigger re-renders, causing the component to show stale state when operating in uncontrolled mode.

Either make the component fully controlled (always require value and onFilesChange) or maintain internal state for uncontrolled usage.

If fully controlled is the intent, apply this diff:

-    const files =
-        value ??
-        (inputRef.current?.files ? Array.from(inputRef.current.files) : [])
+    const files = value ?? []

Then document that value and onFilesChange are required props. Otherwise, add internal state:

+    const [internalFiles, setInternalFiles] = useState<File[]>([])
     const files =
         value ??
-        (inputRef.current?.files ? Array.from(inputRef.current.files) : [])
+        internalFiles

And update handlers to call setInternalFiles when onFilesChange is absent.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const files =
value ??
(inputRef.current?.files ? Array.from(inputRef.current.files) : [])
const files =
value ??
[]

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

♻️ Duplicate comments (1)
fdm-app/app/components/custom/dropzone.tsx (1)

68-70: Fix controlled/uncontrolled state mixing (previously flagged, still unresolved).

Reading from inputRef.current?.files when value is undefined is non-reactive—changes to the input's FileList won't trigger re-renders, causing stale UI in uncontrolled mode.

Since both consumers (mijnpercelen and soil forms) pass value and onFilesChange, this component should be fully controlled.

Apply this diff:

-    const files =
-        value ??
-        (inputRef.current?.files ? Array.from(inputRef.current.files) : [])
+    const files = value ?? []

Then document in the component's JSDoc that value and onFilesChange are required for proper operation.

🧹 Nitpick comments (1)
fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (1)

337-341: Remove commented-out code.

The commented extension labels serve no purpose and add clutter.

Apply this diff:

                                                            <div className="text-xs text-muted-foreground mt-1">
-                                                                {/* .shp,
-                                                                                .shx,
-                                                                                .dbf,
-                                                                                .prj */}
                                                            </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 678b9f5 and 2b7260e.

📒 Files selected for processing (3)
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (5 hunks)
  • fdm-app/app/components/blocks/soil/form-upload.tsx (6 hunks)
  • fdm-app/app/components/custom/dropzone.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-11-04T13:46:46.396Z
Learnt from: BoraIneviNMI
Repo: SvenVw/fdm PR: 0
File: :0-0
Timestamp: 2025-11-04T13:46:46.396Z
Learning: In the FDM app, file upload validation uses a two-layer approach: client-side Zod schemas (e.g., in fdm-app/app/components/blocks/soil/form-upload.tsx) provide early user-facing error feedback before upload, while server-side upload handlers enforce the same limits for security.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
  • fdm-app/app/components/blocks/soil/form-upload.tsx
📚 Learning: 2025-05-09T14:53:44.578Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:53:44.578Z
Learning: In the context of this React Router v7 project, it's important to follow the pattern of importing only the types (like UseFormReturn) from "react-hook-form" while importing the Form component from "react-router" to avoid naming conflicts.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
  • fdm-app/app/components/blocks/soil/form-upload.tsx
📚 Learning: 2025-05-09T14:41:43.484Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/fertilizer-applications/form.tsx:6-6
Timestamp: 2025-05-09T14:41:43.484Z
Learning: The project uses React Router v7 which exports a Form component directly from the "react-router" package, not from "remix-run/react".

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
  • fdm-app/app/components/blocks/soil/form-upload.tsx
📚 Learning: 2025-05-09T14:41:43.484Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/fertilizer-applications/form.tsx:6-6
Timestamp: 2025-05-09T14:41:43.484Z
Learning: The project uses React Router v7 which exports a Form component directly from the "react-router" package, making importing from "remix-run/react" unnecessary.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
  • fdm-app/app/components/blocks/soil/form-upload.tsx
📚 Learning: 2025-05-09T14:58:10.465Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:58:10.465Z
Learning: When updating React components that use both react-hook-form and React Router v7, it's important to only import types (like UseFormReturn, FieldValues) from react-hook-form to avoid naming conflicts with React Router's Form component. Use `import type { ... } from 'react-hook-form'` syntax to ensure only types are imported.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-04-18T13:49:17.029Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 124
File: fdm-app/app/components/custom/farm/farm-title.tsx:3-3
Timestamp: 2025-04-18T13:49:17.029Z
Learning: In the fdm project, NavLink and other routing components can be imported from either "react-router" or "react-router-dom" as react-router-dom is included in react-router.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2024-12-16T10:56:07.561Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 16
File: fdm-app/app/routes/app.addfarm.$b_id_farm.cultivations.$b_lu_catalogue.fertilizers.tsx:1-1
Timestamp: 2024-12-16T10:56:07.561Z
Learning: The project uses `react-router` v7, and the `data` function is exported and used for error handling in loaders and actions.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2024-12-16T10:48:27.598Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 16
File: fdm-app/package.json:40-40
Timestamp: 2024-12-16T10:48:27.598Z
Learning: In the `fdm-app/package.json` file, `remix-toast` and `sonner` are not duplicate dependencies. `remix-toast` manages toast generation and handling within the Remix framework, while `sonner` provides the actual implementation and UI of the toast notifications. They serve different purposes and are intended to be used together in the project.

Applied to files:

  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
📚 Learning: 2025-06-10T15:05:38.163Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 163
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:27-29
Timestamp: 2025-06-10T15:05:38.163Z
Learning: In the fdm-app codebase, the component directory structure is intentionally organized with `~/components/custom/` containing small reusable components that can be used in both blocks and pages, while `~/components/blocks/` contains larger block-level components. Not all components should be migrated from custom to blocks - the custom directory serves as a library of smaller utility components.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-13T11:05:40.105Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 237
File: fdm-data/src/cultivations/catalogues/brp.ts:65-68
Timestamp: 2025-08-13T11:05:40.105Z
Learning: In fdm-app Select components, using `undefined` instead of empty string (`""`) as fallback value prevents empty strings from being submitted as form values. This approach fixes the issue at the UI source rather than requiring backend validation.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-02-13T09:03:11.890Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 71
File: fdm-app/app/routes/farm.create.$b_id_farm.cultivations.$b_lu_catalogue.crop.harvest._index.tsx:111-135
Timestamp: 2025-02-13T09:03:11.890Z
Learning: When adding multiple harvests in fdm-app, use Promise.all instead of Promise.allSettled to ensure atomic behavior - if one harvest addition fails, all should fail and rollback to maintain data consistency.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-11T12:24:32.200Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 233
File: fdm-app/app/components/blocks/atlas-fields/cultivation-history.tsx:53-53
Timestamp: 2025-08-11T12:24:32.200Z
Learning: In `fdm-app/app/components/blocks/atlas-fields/cultivation-history.tsx`, the NMI API for cultivations guarantees that each year will be unique in the cultivation history data, so using `cultivation.year` as a React list key is safe and won't cause duplicate key warnings.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
🧬 Code graph analysis (2)
fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (1)
fdm-app/app/components/custom/dropzone.tsx (1)
  • Dropzone (50-258)
fdm-app/app/components/blocks/soil/form-upload.tsx (1)
fdm-app/app/components/custom/dropzone.tsx (1)
  • Dropzone (50-258)
🪛 Biome (2.1.2)
fdm-app/app/components/custom/dropzone.tsx

[error] 224-224: The HTML element label is non-interactive. Do not use tabIndex.

Adding non-interactive elements to the keyboard navigation flow can confuse users.
Unsafe fix: Remove the tabIndex attribute.

(lint/a11y/noNoninteractiveTabindex)

🔇 Additional comments (14)
fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx (4)

16-16: LGTM! Import added for the new Dropzone component.

The import follows the project's conventions for custom component paths.


114-121: LGTM! Clean derivation of file state from form.

Deriving selectedFiles and hasAllRequiredFiles from form.watch("shapefile") is the correct pattern—form state is the single source of truth, eliminating the need for redundant local state.


129-150: LGTM! Simplified file handler delegates validation to Dropzone.

The refactored handleFilesSet now accepts pre-validated files from the Dropzone component, reducing duplication. DBF parsing logic is correctly preserved.


284-318: LGTM! Dropzone integration with custom merge strategy.

The FormField correctly wires the Dropzone component with:

  • Controlled value from form state
  • onFilesChange callback to update form
  • Custom mergeShapefileParts strategy for handling partial shapefile uploads
  • Proper ref forwarding and validation props

The custom merge function intelligently handles both complete uploads (all required extensions at once) and incremental uploads (one extension at a time).

fdm-app/app/components/custom/dropzone.tsx (6)

22-25: LGTM! Correct edge case handling.

The getFileExtension function now properly handles filenames without extensions by checking for -1 before slicing.


27-48: LGTM! Well-typed props interface.

The DropzoneProps type correctly uses React.Ref<HTMLInputElement> and provides comprehensive prop coverage for controlled usage, file validation, and custom merge strategies.


78-88: LGTM! Correct file deduplication by name.

The default merge strategy now properly deduplicates by filename using a Map, avoiding the reference-equality issue from earlier versions.


90-94: LGTM! Correct input clearing.

Using an empty string instead of null to clear the input value is correct.


113-144: LGTM! Comprehensive file validation.

The checkNewFile function validates extension, min size, and max size with clear user feedback via toast notifications.


260-268: LGTM! Proper context hook implementation.

The useDropzoneContext hook correctly enforces usage within a Dropzone provider with a clear error message.

fdm-app/app/components/blocks/soil/form-upload.tsx (4)

8-8: LGTM! Import added for the new Dropzone component.

Follows the project's conventions for custom component paths.


3-3: LGTM! Efficient memoization for single-file wrapper.

The useMemo prevents unnecessary re-creation of the array wrapper when selectedFile hasn't changed, optimizing the controlled value prop passed to Dropzone.

Also applies to: 41-45


80-83: LGTM! Clean single-file extraction.

The simplified handleFilesChange correctly extracts the first file from the Dropzone's file array and updates the form state.


113-195: LGTM! Clean Dropzone integration for single-file upload.

The FormField correctly integrates Dropzone with:

  • Controlled value via dropzoneValue memo
  • Single-file extraction via handleFilesChange
  • Proper validation props (accept, maxSize, required)
  • Status-based styling and feedback

The implementation is simpler than mijnpercelen because it doesn't need custom merge logic—Dropzone's default single-file behavior (replace) is appropriate here.

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

♻️ Duplicate comments (1)
fdm-app/app/components/custom/dropzone.tsx (1)

68-70: Clarify the component contract: controlled vs. uncontrolled.

The fallback to inputRef.current?.files when value is undefined creates a non-reactive path—changes to the input's FileList won't trigger re-renders, causing stale state if the component is used in uncontrolled mode.

Since the component uses onFilesChange for state updates and relies on value being provided, it appears designed as a controlled component. Either:

  1. Make it fully controlled (recommended):
-    const files =
-        value ??
-        (inputRef.current?.files ? Array.from(inputRef.current.files) : [])
+    const files = value ?? []

Then document that value and onFilesChange are required for the component to function.

  1. Or add internal state for true uncontrolled support:
+    const [internalFiles, setInternalFiles] = useState<File[]>([])
     const files =
         value ??
-        (inputRef.current?.files ? Array.from(inputRef.current.files) : [])
+        internalFiles

And update handlers to call setInternalFiles when onFilesChange is absent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b7260e and 27e7cad.

📒 Files selected for processing (1)
  • fdm-app/app/components/custom/dropzone.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-06-10T15:05:38.163Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 163
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:27-29
Timestamp: 2025-06-10T15:05:38.163Z
Learning: In the fdm-app codebase, the component directory structure is intentionally organized with `~/components/custom/` containing small reusable components that can be used in both blocks and pages, while `~/components/blocks/` contains larger block-level components. Not all components should be migrated from custom to blocks - the custom directory serves as a library of smaller utility components.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-13T11:05:40.105Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 237
File: fdm-data/src/cultivations/catalogues/brp.ts:65-68
Timestamp: 2025-08-13T11:05:40.105Z
Learning: In fdm-app Select components, using `undefined` instead of empty string (`""`) as fallback value prevents empty strings from being submitted as form values. This approach fixes the issue at the UI source rather than requiring backend validation.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-05-09T14:58:10.465Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 138
File: fdm-app/app/components/custom/combobox.tsx:34-37
Timestamp: 2025-05-09T14:58:10.465Z
Learning: When updating React components that use both react-hook-form and React Router v7, it's important to only import types (like UseFormReturn, FieldValues) from react-hook-form to avoid naming conflicts with React Router's Form component. Use `import type { ... } from 'react-hook-form'` syntax to ensure only types are imported.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-02-13T09:03:11.890Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 71
File: fdm-app/app/routes/farm.create.$b_id_farm.cultivations.$b_lu_catalogue.crop.harvest._index.tsx:111-135
Timestamp: 2025-02-13T09:03:11.890Z
Learning: When adding multiple harvests in fdm-app, use Promise.all instead of Promise.allSettled to ensure atomic behavior - if one harvest addition fails, all should fail and rollback to maintain data consistency.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
📚 Learning: 2025-08-11T12:24:32.200Z
Learnt from: SvenVw
Repo: SvenVw/fdm PR: 233
File: fdm-app/app/components/blocks/atlas-fields/cultivation-history.tsx:53-53
Timestamp: 2025-08-11T12:24:32.200Z
Learning: In `fdm-app/app/components/blocks/atlas-fields/cultivation-history.tsx`, the NMI API for cultivations guarantees that each year will be unique in the cultivation history data, so using `cultivation.year` as a React list key is safe and won't cause duplicate key warnings.

Applied to files:

  • fdm-app/app/components/custom/dropzone.tsx
🪛 Biome (2.1.2)
fdm-app/app/components/custom/dropzone.tsx

[error] 224-224: The HTML element label is non-interactive. Do not use tabIndex.

Adding non-interactive elements to the keyboard navigation flow can confuse users.
Unsafe fix: Remove the tabIndex attribute.

(lint/a11y/noNoninteractiveTabindex)

🔇 Additional comments (6)
fdm-app/app/components/custom/dropzone.tsx (6)

1-25: LGTM: Imports and helper function are correct.

The imports follow React 19 patterns with proper type imports, and the getFileExtension helper correctly handles filenames without extensions.


27-48: LGTM: Props are well-typed.

The prop types are appropriate, and the ref prop is correctly typed as React.Ref<HTMLInputElement>.


78-88: LGTM: File deduplication strategy is sound.

The default mergeFiles implementation correctly deduplicates by filename using a Map, avoiding the previous Set-based reference equality issue.


90-188: LGTM: File validation and event handlers are well-implemented.

The file validation logic correctly checks extensions and sizes, provides user feedback via toasts, and properly syncs the native input's FileList using DataTransfer after validation.


202-222: LGTM: Input element and ref forwarding are correct.

The hidden file input properly merges the internal inputRef with the forwarded ref using a callback ref (lines 210-212), ensuring both react-hook-form integration and internal input access work correctly.


190-201: LGTM: Context provider and hook are well-structured.

The context provider correctly exposes file state to children, the conditional reset button respects the allowReset prop, and the useDropzoneContext hook properly enforces usage within a Dropzone component.

Also applies to: 244-268

Comment thread fdm-app/app/components/custom/dropzone.tsx
@BoraIneviNMI BoraIneviNMI merged commit 50f974d into development Nov 10, 2025
10 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants