Skip to content

Add farm dashboard, fields table, batch fertilizer route, fuzzysort#274

Merged
SvenVw merged 36 commits into
developmentfrom
FDM-269
Sep 25, 2025
Merged

Add farm dashboard, fields table, batch fertilizer route, fuzzysort#274
SvenVw merged 36 commits into
developmentfrom
FDM-269

Conversation

@SvenVw
Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw commented Sep 23, 2025

Summary by CodeRabbit

  • New Features

    • Farm dashboard: overview, quick actions, and links to farm apps.
    • Advanced Fields table: searchable fuzzy search, sortable columns, column visibility, responsive layout, multi-row selection, and in-table actions.
    • Multi-field fertilizer workflow: select fields, configure application, validate, and apply with confirmations and toasts.
  • Improvements

    • Fields page: new table layout, updated breadcrumbs, clearer titles and empty states.
    • Selection editing dialog and improved badges/UI for field details.
  • Chores

    • Added fuzzy-search dependency for table search.

Closes #269

@SvenVw SvenVw self-assigned this Sep 23, 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 (1)
fdm-app/app/components/blocks/fields/table.tsx (1)

119-129: Consider optimizing the fuzzy filter performance.

The current implementation recreates search strings for every filter call. For better performance with frequently changing data, consider using fuzzysort.prepare() to pre-process targets or memoizing the concatenated search strings.

+const useMemo(() => {
+  return data.map(item => ({
+    ...item,
+    searchTarget: `${item.b_name} ${item.cultivations.map(c => c.b_lu_name).join(' ')} ${item.fertilizerApplications.map(f => f.p_name_nl).join(' ')} ${item.b_soiltype_agr}`
+  }))
+}, [data])

const fuzzyFilter: FilterFn<TData> = (row, _columnId, filterValue) => {
-  const cultivationNames = row.original.cultivations
-    .map((c: { b_lu_name: string }) => c.b_lu_name)
-    .join(" ")
-  const fertilizerNames = row.original.fertilizerApplications
-    .map((f: { p_name_nl: string }) => f.p_name_nl)
-    .join(" ")
-  const target = `${row.getValue("b_name")} ${cultivationNames} ${fertilizerNames} ${row.getValue("b_soiltype_agr")}`
-  const result = fuzzysort.go(filterValue, [target])
+  const result = fuzzysort.go(filterValue, [row.original.searchTarget])
  return result.length > 0
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e0083b and 9d0fa31.

📒 Files selected for processing (8)
  • .changeset/purple-dryers-rule.md (1 hunks)
  • .changeset/shaggy-snails-tickle.md (1 hunks)
  • .changeset/wicked-boats-hope.md (1 hunks)
  • fdm-app/app/components/blocks/fields/columns.tsx (1 hunks)
  • fdm-app/app/components/blocks/fields/table.tsx (1 hunks)
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx (6 hunks)
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx (1 hunks)
  • fdm-app/app/routes/farm._index.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • fdm-app/app/routes/farm._index.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • .changeset/wicked-boats-hope.md
  • .changeset/purple-dryers-rule.md
  • fdm-app/app/components/blocks/fields/columns.tsx
🧰 Additional context used
🧠 Learnings (14)
📓 Common learnings
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:151-204
Timestamp: 2025-09-23T12:27:07.359Z
Learning: In the FDM application, field overview functionality is implemented as a dedicated page accessible via `farm/{farmId}/{calendar}/field` rather than as a direct listing on the dashboard. The dashboard includes a "Perceelsoverzicht" quick action card that provides navigation to this comprehensive field management interface.
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:160-163
Timestamp: 2025-09-23T12:29:34.158Z
Learning: In the FDM application, the fertilizer application route intentionally uses `${calendar}/field/fertilizer` instead of the originally planned `/farm/{farmId}/add/fertilizer` structure. This design decision prioritizes starting from the field list view to provide better field selection workflow before applying fertilizer, rather than direct dashboard-to-action navigation.
Learnt from: SvenVw
PR: SvenVw/fdm#42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A comprehensive farm layout system has been created in `components/custom/farm-layouts/` with `BaseFarmLayout` and `FarmSidebarLayout` components. The system supports both simple and sidebar-based layouts while maintaining consistent header and farm selection functionality across all farm routes.
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
PR: SvenVw/fdm#42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A shared layout component `FarmLayoutBase` has been created in `components/custom/farm-layout-base.tsx` to maintain consistency across farm-related pages. The component handles farm selection dropdown, breadcrumb navigation, and provides a common layout structure.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
📚 Learning: 2025-09-23T12:29:34.158Z
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:160-163
Timestamp: 2025-09-23T12:29:34.158Z
Learning: In the FDM application, the fertilizer application route intentionally uses `${calendar}/field/fertilizer` instead of the originally planned `/farm/{farmId}/add/fertilizer` structure. This design decision prioritizes starting from the field list view to provide better field selection workflow before applying fertilizer, rather than direct dashboard-to-action navigation.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
  • .changeset/shaggy-snails-tickle.md
📚 Learning: 2025-09-23T12:27:07.359Z
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:151-204
Timestamp: 2025-09-23T12:27:07.359Z
Learning: In the FDM application, field overview functionality is implemented as a dedicated page accessible via `farm/{farmId}/{calendar}/field` rather than as a direct listing on the dashboard. The dashboard includes a "Perceelsoverzicht" quick action card that provides navigation to this comprehensive field management interface.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
  • .changeset/shaggy-snails-tickle.md
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
PR: SvenVw/fdm#42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: The `FarmLayout` component in `components/custom/farm-layout.tsx` provides a reusable layout structure for farm-related pages, with support for farm selection dropdown, customizable breadcrumb titles, and flexible content rendering through either children or Outlet components.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
PR: SvenVw/fdm#42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: The farm layout system has been reorganized into separate components (`FarmHeader`, `ContentLayout`, `PaginationLayout`) to support different navigation patterns (sidebar, pagination) while maintaining consistent styling. Each layout component is designed to be used independently or combined as needed.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
📚 Learning: 2025-09-23T12:37:58.700Z
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx:113-148
Timestamp: 2025-09-23T12:37:58.700Z
Learning: In the FDM application, the current field data fetching implementation using Promise.all with individual API calls (getCultivations, getFertilizerApplications, getCurrentSoilData) performs acceptably even with farms containing 90+ fields. No performance issues have been observed in practice with this approach.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
  • .changeset/shaggy-snails-tickle.md
📚 Learning: 2025-01-09T16:03:37.764Z
Learnt from: SvenVw
PR: SvenVw/fdm#42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A comprehensive farm layout system has been created in `components/custom/farm-layouts/` with `BaseFarmLayout` and `FarmSidebarLayout` components. The system supports both simple and sidebar-based layouts while maintaining consistent header and farm selection functionality across all farm routes.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
📚 Learning: 2025-01-23T15:17:23.028Z
Learnt from: SvenVw
PR: SvenVw/fdm#49
File: fdm-app/app/routes/farm.create.$b_id_farm.atlas.tsx:208-208
Timestamp: 2025-01-23T15:17:23.028Z
Learning: The `addField` function in fdm-core should use database transactions and field verification to ensure field availability before resolving its promise, eliminating the need for sleep workarounds.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
📚 Learning: 2025-04-04T14:27:39.518Z
Learnt from: SvenVw
PR: SvenVw/fdm#116
File: fdm-app/app/routes/farm.$b_id_farm.$calendar.field.new.tsx:111-154
Timestamp: 2025-04-04T14:27:39.518Z
Learning: In the FDM application, cultivation retrieval logic should be centralized in utility functions rather than duplicated across loader and action functions to improve maintainability and ensure consistent behavior.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
📚 Learning: 2025-02-13T09:03:11.890Z
Learnt from: SvenVw
PR: SvenVw/fdm#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/routes/farm.$b_id_farm.$calendar.field._index.tsx
📚 Learning: 2025-09-23T10:02:32.123Z
Learnt from: BoraIneviNMI
PR: SvenVw/fdm#272
File: fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue.manage.$p_id.tsx:151-164
Timestamp: 2025-09-23T10:02:32.123Z
Learning: The getFertilizer function from svenvw/fdm-core throws an exception if the fertilizer doesn't exist, rather than returning null or undefined.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
📚 Learning: 2024-12-19T13:20:44.152Z
Learnt from: SvenVw
PR: SvenVw/fdm#23
File: fdm-app/app/routes/app.addfarm.new.tsx:15-17
Timestamp: 2024-12-19T13:20:44.152Z
Learning: Authentication for the “app.addfarm.new” route is already handled globally in “fdm-app/app/routes/app.tsx,” automatically redirecting unauthenticated users to the SignIn page.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
📚 Learning: 2025-06-02T10:31:27.097Z
Learnt from: SvenVw
PR: SvenVw/fdm#151
File: fdm-app/app/routes/signin._index.tsx:101-101
Timestamp: 2025-06-02T10:31:27.097Z
Learning: In fdm-app/app/routes/signin._index.tsx, the redirect destinations are intentionally inconsistent by design: the component defaults new sign-ins to "/welcome" (line 101) while the loader redirects authenticated users to "/farm" (line 80) and the action uses "/farm" as fallback (line 434). This creates appropriate user flows where new users complete their profile via the welcome page, while existing authenticated users bypass it and go directly to the main application.

Applied to files:

  • fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
🧬 Code graph analysis (2)
fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx (8)
fdm-core/src/cultivation.ts (1)
  • getCultivations (445-513)
fdm-core/src/fertilizer.ts (1)
  • getFertilizerApplications (912-978)
fdm-core/src/soil.ts (1)
  • getCurrentSoilData (418-567)
fdm-app/app/components/blocks/header/base.tsx (1)
  • Header (6-29)
fdm-app/app/components/blocks/farm/farm-title.tsx (1)
  • FarmTitle (15-36)
fdm-app/app/components/blocks/farm/farm-content.tsx (1)
  • FarmContent (13-27)
fdm-app/app/components/blocks/fields/table.tsx (1)
  • DataTable (50-338)
fdm-app/app/components/blocks/fields/columns.tsx (1)
  • columns (33-259)
fdm-app/app/components/blocks/fields/table.tsx (1)
fdm-app/app/components/blocks/fields/columns.tsx (2)
  • FieldExtended (17-31)
  • columns (33-259)
🔇 Additional comments (14)
.changeset/shaggy-snails-tickle.md (1)

1-6: LGTM!

The changeset properly documents a minor version bump for the addition of a new UI page for bulk fertilizer applications across multiple fields.

fdm-app/app/components/blocks/fields/table.tsx (3)

229-229: URL encode field IDs to prevent query parameter issues.

Field IDs may contain special characters that could break the URL query parameters. The current implementation uses encodeURIComponent which is correct.


78-117: Shift-click range selection logic is solid.

The implementation correctly handles shift-click range selection with proper bounds checking and index management. The link click detection prevents interference with navigation.


18-19: fuzzysort dependency verified

fdm-app/package.json lists "fuzzysort": "^3.1.0" — no change required.

fdm-app/app/routes/farm.$b_id_farm.$calendar.field._index.tsx (4)

135-136: Safe handling of optional soil parameters.

The optional chaining with nullish coalescing properly handles cases where soil parameters might not be present in the data, preventing runtime errors.


113-148: Data fetching performance is acceptable for current scale.

Based on the retrieved learning, this Promise.all implementation performs well even with 90+ fields in practice, so no optimization is needed at this time.


26-29: Well-structured component imports for the new DataTable system.

The imports properly reference the new reusable DataTable, columns, FarmContent, and breadcrumb components, following the established component architecture.


230-237: Consistent integration with the new DataTable.

The component properly integrates the new DataTable with the enhanced field data (fieldsExtended) and maintains the established layout patterns with FarmContent.

fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx (6)

85-88: Robust query parameter parsing for field selection.

The implementation properly handles URL search parameters, splits field IDs, and filters out empty values. This provides a clean way to pass selected field IDs from the DataTable.


146-168: Correct fertilizer parameter processing.

The implementation safely processes application method options with proper null checking and type filtering. The error handling for missing parameter metadata is appropriate.


440-455: Sequential fertilizer application with proper error handling.

The loop applies fertilizer to each selected field sequentially. This approach ensures data consistency - if one application fails, the entire operation will fail and rollback properly.


461-466: Comprehensive error handling for form validation.

The action properly catches Zod validation errors and provides user-friendly error messages, while allowing other errors to be handled by the error boundary.


245-253: Excellent loading state UX.

The loading overlay with spinner and descriptive text provides clear feedback during the potentially time-consuming bulk fertilizer application process.


310-384: Well-designed field selection dialog.

The dialog implementation provides intuitive field selection with checkboxes, proper state management, and responsive design. The search parameter updates maintain URL consistency.

@SvenVw SvenVw requested a review from gerardhros September 23, 2025 13:16
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

🧹 Nitpick comments (3)
fdm-app/app/components/blocks/fields/table.tsx (3)

127-130: Global fuzzy filter should no-op on empty input

With an empty search, fuzzysort may return no results, unintentionally filtering out all rows. Short-circuit to show all rows when filter is empty.

Apply this diff:

-    const fuzzyFilter: FilterFn<TData> = (row, _columnId, filterValue) => {
-        const result = fuzzysort.go(filterValue, [(row.original as any).searchTarget])
-        return result.length > 0
-    }
+    const fuzzyFilter: FilterFn<TData> = (row, _columnId, filterValue) => {
+        if (!filterValue) return true
+        const haystack = (row.original as any).searchTarget as string
+        return !!fuzzysort.single(String(filterValue), haystack)
+    }

162-166: Harden quick-action links: gate on missing params and make “Nieuw perceel” absolute

Ensure we don’t generate broken links when params are absent, and make the “Nieuw perceel” link work in both dashboard and field-list contexts.

Apply these diffs:

@@
-    const isFertilizerButtonDisabled = selectedFields.length === 0
+    const isFertilizerButtonDisabled = selectedFields.length === 0
+    const canNavigateFertilizer =
+        !isFertilizerButtonDisabled && Boolean(b_id_farm && calendar)
@@
-                                    {isFertilizerButtonDisabled ? (
+                                    {!canNavigateFertilizer ? (
                                         <Button
-                                            disabled={
-                                                isFertilizerButtonDisabled
-                                            }
+                                            disabled
                                         >
                                             <Plus className="mr-2 h-4 w-4" />
                                             Bemesting
                                         </Button>
                                     ) : (
                                         <NavLink
                                             to={`/farm/${b_id_farm}/${calendar}/field/fertilizer?fieldIds=${selectedFieldIds.map(encodeURIComponent).join(",")}`}
                                         >
                                             <Button>
                                                 <Plus className="mr-2 h-4 w-4" />
                                                 Bemesting
                                             </Button>
                                         </NavLink>
                                     )}
-                                <NavLink to={"./new"}>
+                                <NavLink
+                                    to={
+                                        b_id_farm && calendar
+                                            ? `/farm/${b_id_farm}/${calendar}/field/new`
+                                            : "./new"
+                                    }
+                                >
                                     <Button>
                                         <Plus className="mr-2 h-4 w-4" />
                                         Nieuw perceel
                                     </Button>
-                                </NavLink>
+                                </NavLink>

Also applies to: 219-237, 248-253


120-126: Optional: type-safety for augmented data (remove any)

You add searchTarget in memoizedData but later cast row.original as any. Consider typing the augmentation to avoid any:

  • Annotate: const memoizedData = useMemo<Array<TData & { searchTarget: string }>>(...
  • Update the filter type to FilterFn<TData & { searchTarget: string }> and drop the cast.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d0fa31 and d8963c3.

📒 Files selected for processing (1)
  • fdm-app/app/components/blocks/fields/table.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:151-204
Timestamp: 2025-09-23T12:27:07.359Z
Learning: In the FDM application, field overview functionality is implemented as a dedicated page accessible via `farm/{farmId}/{calendar}/field` rather than as a direct listing on the dashboard. The dashboard includes a "Perceelsoverzicht" quick action card that provides navigation to this comprehensive field management interface.
Learnt from: SvenVw
PR: SvenVw/fdm#274
File: fdm-app/app/routes/farm.$b_id_farm._index.tsx:160-163
Timestamp: 2025-09-23T12:29:34.158Z
Learning: In the FDM application, the fertilizer application route intentionally uses `${calendar}/field/fertilizer` instead of the originally planned `/farm/{farmId}/add/fertilizer` structure. This design decision prioritizes starting from the field list view to provide better field selection workflow before applying fertilizer, rather than direct dashboard-to-action navigation.
Learnt from: SvenVw
PR: SvenVw/fdm#42
File: fdm-app/app/routes/farm/_b_id_farm/layout.tsx:46-95
Timestamp: 2025-01-09T16:03:37.764Z
Learning: A comprehensive farm layout system has been created in `components/custom/farm-layouts/` with `BaseFarmLayout` and `FarmSidebarLayout` components. The system supports both simple and sidebar-based layouts while maintaining consistent header and farm selection functionality across all farm routes.
🧬 Code graph analysis (1)
fdm-app/app/components/blocks/fields/table.tsx (2)
fdm-app/app/components/blocks/fields/columns.tsx (2)
  • FieldExtended (17-31)
  • columns (33-259)
fdm-app/app/components/blocks/fertilizer/table.tsx (1)
  • DataTable (30-132)

Comment thread fdm-app/app/components/blocks/fields/table.tsx Outdated
Comment thread fdm-app/app/routes/farm.$b_id_farm.$calendar.field.fertilizer.tsx
Copy link
Copy Markdown
Collaborator

@gerardhros gerardhros left a comment

Choose a reason for hiding this comment

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

LGTM. one minor question.

@SvenVw SvenVw merged commit 48fe689 into development Sep 25, 2025
9 of 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.

Add Farm Overview Dashboard with Quick Actions

2 participants