Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/free-onions-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@svenvw/fdm-app": minor
---

Users can now edit previously created fertilizer applications, both for individual fields or a given cultivation type.
88 changes: 76 additions & 12 deletions fdm-app/app/components/blocks/fertilizer-applications/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ export function FertilizerApplicationCard({
const params = useParams()
const navigation = useNavigation()
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [editedFertilizerApplication, setEditedFertilizerApplication] =
useState<FertilizerApplication>()
const previousNavigationState = useRef(navigation.state)

const b_id_or_b_lu_catalogue = params.b_lu_catalogue || params.b_id
Expand All @@ -165,27 +167,70 @@ export function FertilizerApplicationCard({
fetcher.submit({ p_app_id }, { method: "DELETE" })
}

const handleEdit = (fertilizerApplication: FertilizerApplication) => () => {
setEditedFertilizerApplication(fertilizerApplication)
setIsDialogOpen(true)
}

useEffect(() => {
const wasNotIdle = previousNavigationState.current !== "idle"
const isIdle = navigation.state === "idle"

if (wasNotIdle && isIdle) {
setIsDialogOpen(false)
setEditedFertilizerApplication(undefined)
}

previousNavigationState.current = navigation.state
}, [navigation.state])

const fieldFertilizerFormStore = useFieldFertilizerFormStore()
const savedFormValues =
params.b_id_farm &&
b_id_or_b_lu_catalogue &&
fieldFertilizerFormStore.load(params.b_id_farm, b_id_or_b_lu_catalogue)
params.b_id_farm && b_id_or_b_lu_catalogue
? fieldFertilizerFormStore.load(
params.b_id_farm,
b_id_or_b_lu_catalogue,
)
: null

// See if the saved form was for updating an existing application.
// If so, verify that the user can still edit the application and update the state.
const applicationToEdit = savedFormValues?.p_app_id
? fertilizerApplications.find(
(app) => app.p_app_id === savedFormValues.p_app_id,
)
: null
useEffect(() => {
if (applicationToEdit && !editedFertilizerApplication) {
setEditedFertilizerApplication(applicationToEdit)
}
if (savedFormValues?.p_app_id && !applicationToEdit) {
fieldFertilizerFormStore.delete(
params.b_id_farm || "",
b_id_or_b_lu_catalogue || "",
)
}
}, [
applicationToEdit,
params.b_id_farm,
b_id_or_b_lu_catalogue,
savedFormValues,
editedFertilizerApplication,
fieldFertilizerFormStore.delete,
])

useEffect(() => {
if (!isDialogOpen && savedFormValues) {
setIsDialogOpen(true)
if (savedFormValues && !isDialogOpen) {
if (savedFormValues.p_app_id) {
// Do not open the form if there is a risk it will create a new application
if (applicationToEdit) {
setIsDialogOpen(true)
}
} else {
setIsDialogOpen(true)
}
}
}, [isDialogOpen, savedFormValues])
}, [savedFormValues, applicationToEdit, isDialogOpen])

const detailCards = constructCards(dose)

Expand All @@ -197,6 +242,10 @@ export function FertilizerApplicationCard({
)
}

if (!state) {
setEditedFertilizerApplication(undefined)
}

setIsDialogOpen(state)
}

Expand All @@ -206,8 +255,8 @@ export function FertilizerApplicationCard({
<CardTitle>
<p className="text-lg font-medium">Bemesting</p>
<p className="text-sm font-medium text-muted-foreground">
Voeg bemestingen toe, verwijder ze en bekijk de totale
gift per hectare voor verschillende nutriënten
Voeg bemestingen toe, wijzig of verwijder ze en bekijk
de totale gift per hectare voor verschillende nutriënten
</p>
</CardTitle>
<Dialog
Expand All @@ -220,11 +269,14 @@ export function FertilizerApplicationCard({
<DialogContent className="sm:max-w-[800px]">
<DialogHeader>
<DialogTitle className="flex flex-row items-center justify-between mr-4">
Bemesting toevoegen
{editedFertilizerApplication
? "Bemesting wijzigen"
: "Bemesting toevoegen"}
</DialogTitle>
<DialogDescription>
Voeg een nieuwe bemestingstoepassing toe aan het
perceel.
{editedFertilizerApplication
? "Wijzig een bemestingtoepassing aan het percel."
: "Voeg een nieuwe bemestingstoepassing toe aan het perceel."}
</DialogDescription>
</DialogHeader>
<FertilizerApplicationForm
Expand All @@ -235,6 +287,7 @@ export function FertilizerApplicationCard({
b_id_or_b_lu_catalogue={
b_id_or_b_lu_catalogue || ""
}
fertilizerApplication={editedFertilizerApplication}
/>
</DialogContent>
</Dialog>
Expand All @@ -244,7 +297,7 @@ export function FertilizerApplicationCard({
{fertilizerApplications.length > 0 ? (
fertilizerApplications.map((application) => (
<div
className="grid grid-cols-5 gap-x-3 items-center"
className="grid grid-cols-6 gap-x-3 items-center"
key={application.p_app_id}
>
<div className="col-span-2">
Expand Down Expand Up @@ -274,6 +327,17 @@ export function FertilizerApplicationCard({
)}
</p>
</div>
<div className="justify-self-end">
<Button
variant="secondary"
disabled={
fetcher.state === "submitting"
}
onClick={handleEdit(application)}
>
Wijzig
</Button>
</div>
<div className="justify-self-end">
<Button
variant="destructive"
Expand Down
51 changes: 38 additions & 13 deletions fdm-app/app/components/blocks/fertilizer-applications/form.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { zodResolver } from "@hookform/resolvers/zod"
import type { FertilizerApplication } from "@svenvw/fdm-core"
import { Plus } from "lucide-react"
import type { MouseEvent } from "react"
import { useEffect } from "react"
import { useEffect, useId } from "react"
import type { Navigation } from "react-router"
import { Form, useNavigate, useSearchParams } from "react-router"
import { RemixFormProvider, useRemixForm } from "remix-hook-form"
import type { z } from "zod"
import { useFieldFertilizerFormStore } from "@/app/store/field-fertilizer-form"
import { Combobox } from "~/components/custom/combobox"
import { DatePicker } from "~/components/custom/date-picker"
Expand All @@ -32,7 +32,11 @@ import {
TooltipContent,
TooltipTrigger,
} from "~/components/ui/tooltip"
import { FormSchema } from "./formschema"
import {
type FieldFertilizerFormValues,
FormSchema,
FormSchemaModify,
} from "./formschema"
import type { FertilizerOption } from "./types.d"

export function FertilizerApplicationForm({
Expand All @@ -41,35 +45,48 @@ export function FertilizerApplicationForm({
navigation,
b_id_farm,
b_id_or_b_lu_catalogue,
fertilizerApplication,
}: {
options: FertilizerOption[]
action: string
navigation: Navigation
b_id_farm: string
b_id_or_b_lu_catalogue: string
fertilizerApplication: FertilizerApplication
}) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const navigate = useNavigate()
const [searchParams] = useSearchParams()

const form = useRemixForm<z.infer<typeof FormSchema>>({
const formId = useId()
const form = useRemixForm<FieldFertilizerFormValues>({
mode: "onTouched",
resolver: zodResolver(FormSchema),
resolver: zodResolver(
fertilizerApplication ? FormSchemaModify : FormSchema,
),
defaultValues: {
p_id: undefined,
p_app_method: undefined,
p_app_amount: undefined,
p_app_date: new Date(),
p_app_id: fertilizerApplication?.p_app_ids
? fertilizerApplication.p_app_ids.join(",")
: fertilizerApplication?.p_app_id,
p_id: fertilizerApplication?.p_id,
p_app_method: fertilizerApplication?.p_app_method,
p_app_amount: undefined, // Handled through an effect due to blank behavior
p_app_date: fertilizerApplication?.p_app_date ?? new Date(),
},
submitConfig: {
method: fertilizerApplication ? "PUT" : "POST",
},
Comment on lines +74 to 76
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 | 🔴 Critical

🧩 Analysis chain

Use PATCH to match the server (or accept both).

Align client method to server-side handling.

-        submitConfig: {
-            method: fertilizerApplication ? "PUT" : "POST",
-        },
+        submitConfig: {
+            method: fertilizerApplication ? "PATCH" : "POST",
+        },

If you prefer PUT, keep this as PUT and rely on the server change from my other comment.


I need to verify the server-side expectations for the HTTP method used when updating fertilizer applications. Let me search the codebase for API endpoint definitions and related documentation.


🏁 Script executed:

#!/bin/bash
# Search for fertilizer application related API routes or endpoint definitions
rg -n -i 'fertilizer.*application' --type=ts --type=js --type=tsx --type=jsx -C3 | head -50

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for PUT or PATCH method handling in routes
rg -n '(PUT|PATCH)' --type=ts --type=js -C2 | grep -i 'fertilizer\|application' -C2 | head -50

Length of output: 1077


🏁 Script executed:

#!/bin/bash
# Search for API endpoint definitions
fd -e ts -e js -e tsx -e jsx | xargs grep -l 'router\.\(put\|patch\)' | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for server/API related files
fd -path '*/api/*' -o -path '*/server/*' -o -path '*/routes/*' | grep -i fertilizer | head -20

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Search for any documentation or comments about this endpoint
rg -n 'fertilizerApplication' --type=ts --type=tsx -C2 | head -50

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check if there are type definitions or interfaces for the fertilizer endpoint
rg -n 'fertilizer' --type=ts --type=tsx | grep -i 'endpoint\|method\|api' | head -20

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Look for submitConfig usage patterns
rg -n 'submitConfig.*method' --type=ts --type=tsx -C1 | head -30

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for any API documentation files
fd -e md -o -e txt | xargs grep -l -i 'fertilizer' 2>/dev/null | head -10

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Check the form.tsx file context around line 71-73
fd 'form.tsx' | xargs grep -n 'submitConfig\|method.*PUT\|method.*POST' -B5 -A5

Length of output: 1324


🏁 Script executed:

#!/bin/bash
# Search for other references to PUT vs PATCH in the codebase
rg -n '"PUT"|"PATCH"' --type=ts --type=tsx | head -30

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Look for Swagger/OpenAPI specs that might document the API
fd -e json -e yaml | xargs grep -l -i 'fertilizer' 2>/dev/null | head -10

Length of output: 441


🏁 Script executed:

#!/bin/bash
# Check for environment files or config that might have API definitions
find . -name '*.env*' -o -name '*.config.*' -o -name '*api*config*' | head -20

Length of output: 369


🏁 Script executed:

#!/bin/bash
# Search for the actual form component to understand the complete context
cat fdm-app/app/components/blocks/fertilizer-applications/form.tsx | head -100

Length of output: 3370


🏁 Script executed:

#!/bin/bash
# Look for PUT handler in the server code
rg -n '\.put\(|router\.put' --type=ts --type=js -C3 | head -50

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check package.json to understand the tech stack
cat package.json | grep -A5 -B5 'fertilizer\|axios\|fetch\|http'

Length of output: 306


🏁 Script executed:

#!/bin/bash
# Search for PATCH handler in the server code
rg -n '\.patch\(|router\.patch' --type=ts --type=js -C3 | head -50

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for any migration files or changelogs mentioning this
fd -e md -e txt -e sql | xargs grep -l -i 'http.*method\|PUT\|PATCH' 2>/dev/null | head -10

Length of output: 354


🏁 Script executed:

#!/bin/bash
# Check if there's a specific API client or service file
fd 'api\|service' -type f | grep -i fertilizer

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for any tests that might clarify the expected behavior
fd -e test.ts -e test.tsx -e spec.ts | xargs grep -l 'fertilizer' 2>/dev/null | head -10

Length of output: 501


🏁 Script executed:

#!/bin/bash
# Look for constants or enums related to HTTP methods
rg -n 'HTTP_METHOD\|REQUEST_METHOD' --type=ts --type=tsx -C2 | head -30

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check git history for related changes (non-heavy command)
git log --oneline --all -i --grep='fertilizer\|PATCH\|PUT' | head -20

Length of output: 1376


🏁 Script executed:

#!/bin/bash
# Search for any error handling related to the fertilizer endpoint
rg -n 'fertilizerApplication' --type=ts --type=tsx -A10 -B10 | grep -i 'error\|method\|PUT\|PATCH' -C2 | head -50

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Look for the actual server implementation
fd -path '*/src/server/*' -o -path '*/api/*' -type f | grep -v node_modules | head -20

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Check if there's an API route definition file
find . -name '*route*' -o -name '*endpoint*' | grep -v node_modules | head -20

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Search for form submission handlers
rg -n 'onSubmit\|submit' fdm-app/app/components/blocks/fertilizer-applications/form.tsx -C3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the complete form.tsx file
wc -l fdm-app/app/components/blocks/fertilizer-applications/form.tsx && echo "---" && cat fdm-app/app/components/blocks/fertilizer-applications/form.tsx

Length of output: 13068


🏁 Script executed:

#!/bin/bash
# Search for any API specs or documentation
fd -name '*.openapi*' -o -name '*.swagger*' -o -name '*api*.json' -o -name '*api*.yaml' | head -10

Length of output: 230


Change PUT to PATCH to align with server-side route handler.

The server at fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx:216 explicitly checks if (request.method === "PATCH"), but the client currently sends PUT for updates. This mismatch will cause the update request to fail server-side processing.

-        submitConfig: {
-            method: fertilizerApplication ? "PUT" : "POST",
-        },
+        submitConfig: {
+            method: fertilizerApplication ? "PATCH" : "POST",
+        },
📝 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
submitConfig: {
method: fertilizerApplication ? "PUT" : "POST",
},
submitConfig: {
method: fertilizerApplication ? "PATCH" : "POST",
},
🤖 Prompt for AI Agents
In fdm-app/app/components/blocks/fertilizer-applications/form.tsx around lines
71 to 73, the submitConfig.method uses "PUT" for updates but the server route
checks for "PATCH"; change the conditional so updates send "PATCH" (i.e.,
method: fertilizerApplication ? "PATCH" : "POST") to match the server-side
handler and ensure update requests are processed.

})
const p_id = form.watch("p_id")
const selectedFertilizer = options.find((option) => option.value === p_id)
const isSubmitting = navigation.state === "submitting"

useEffect(() => {
if (p_id) {
if (
p_id &&
(!fertilizerApplication || fertilizerApplication.p_id !== p_id)
) {
form.setValue("p_app_method", "")
}
}, [p_id, form.setValue])
}, [p_id, fertilizerApplication, form.setValue])

const fieldFertilizerFormStore = useFieldFertilizerFormStore()

Expand Down Expand Up @@ -97,6 +114,12 @@ export function FertilizerApplicationForm({
fieldFertilizerFormStore.load,
])

useEffect(() => {
if (fertilizerApplication) {
form.setValue("p_app_amount", fertilizerApplication.p_app_amount)
}
}, [fertilizerApplication, form.setValue])

// Change fertilizer selection if the user has added a new fertilizer
const new_p_id = searchParams.get("p_id")
useEffect(() => {
Expand Down Expand Up @@ -134,7 +157,7 @@ export function FertilizerApplicationForm({
return (
<RemixFormProvider {...form}>
<Form
id="formAddFertilizerApplication"
id={formId}
action={action}
onSubmit={form.handleSubmit}
method="post"
Expand Down Expand Up @@ -270,6 +293,8 @@ export function FertilizerApplicationForm({
<LoadingSpinner />
<span>Opslaan...</span>
</div>
) : fertilizerApplication ? (
"Opslaan"
) : (
"Voeg toe"
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,15 @@ export const FormSchema = z.object({
invalid_type_error: "Meststof is ongeldig",
}),
})

export const FormSchemaModify = FormSchema.extend({
p_app_id: z.string({
// TODO: Validate against the options that are available
required_error: "Bemesting id is verplicht",
invalid_type_error: "Bemesting id is ongeldig",
}),
})

export type FieldFertilizerFormValues = z.infer<typeof FormSchema> & {
p_app_id?: string | undefined
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getFertilizers,
getField,
removeFertilizerApplication,
updateFertilizerApplication,
} from "@svenvw/fdm-core"
import {
type ActionFunctionArgs,
Expand All @@ -16,7 +17,10 @@ import {
} from "react-router"
import { dataWithError, dataWithSuccess } from "remix-toast"
import { FertilizerApplicationCard } from "~/components/blocks/fertilizer-applications/card"
import { FormSchema } from "~/components/blocks/fertilizer-applications/formschema"
import {
FormSchema,
FormSchemaModify,
} from "~/components/blocks/fertilizer-applications/formschema"
import { getSession } from "~/lib/auth.server"
import { getTimeframe } from "~/lib/calendar"
import { clientConfig } from "~/lib/config"
Expand Down Expand Up @@ -209,14 +213,45 @@ export async function action({ request, params }: ActionFunctionArgs) {
)
}

if (request.method === "PUT") {
// Collect form entry
const formValues = await extractFormValuesFromRequest(
request,
FormSchemaModify,
)
const { p_app_id, p_id, p_app_amount, p_app_date, p_app_method } =
formValues

if (!p_app_id || typeof p_app_id !== "string") {
return dataWithError(
"Invalid or missing p_app_id value",
"Helaas, er is wat misgegaan. Probeer het later opnieuw of neem contact op met ondersteuning.",
)
}

await updateFertilizerApplication(
fdm,
session.principal_id,
p_app_id,
p_id,
p_app_amount,
p_app_method,
p_app_date,
)

return dataWithSuccess("Date edited successfully", {
message: "Bemesting is gewijzigd",
})
}
Comment on lines +216 to +245
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 | 🟡 Minor

Fix typos in user-facing messages.

The PUT handler logic is correct, but there are two minor typos:

  1. Line 228: "misggegaan" should be "misgegaan" (one 'g')
  2. Line 242: "Date edited successfully" should likely be "Data edited successfully" (to match the pattern used elsewhere in the codebase)

Apply this diff:

                 return dataWithError(
                     "Invalid or missing p_app_id value",
-                    "Helaas, er is wat misggegaan. Probeer het later opnieuw of neem contact op met ondersteuning.",
+                    "Helaas, er is wat misgegaan. Probeer het later opnieuw of neem contact op met ondersteuning.",
                 )
             }

             await updateFertilizerApplication(
                 fdm,
                 session.principal_id,
                 p_app_id,
                 p_id,
                 p_app_amount,
                 p_app_method,
                 p_app_date,
             )

-            return dataWithSuccess("Date edited successfully", {
+            return dataWithSuccess("Data edited successfully", {
                 message: "Bemesting is gewijzigd",
             })
📝 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
if (request.method === "PUT") {
// Collect form entry
const formValues = await extractFormValuesFromRequest(
request,
FormSchemaModify,
)
const { p_app_id, p_id, p_app_amount, p_app_date, p_app_method } =
formValues
if (!p_app_id || typeof p_app_id !== "string") {
return dataWithError(
"Invalid or missing p_app_id value",
"Helaas, er is wat misggegaan. Probeer het later opnieuw of neem contact op met ondersteuning.",
)
}
await updateFertilizerApplication(
fdm,
session.principal_id,
p_app_id,
p_id,
p_app_amount,
p_app_method,
p_app_date,
)
return dataWithSuccess("Date edited successfully", {
message: "Bemesting is gewijzigd",
})
}
if (request.method === "PUT") {
// Collect form entry
const formValues = await extractFormValuesFromRequest(
request,
FormSchemaModify,
)
const { p_app_id, p_id, p_app_amount, p_app_date, p_app_method } =
formValues
if (!p_app_id || typeof p_app_id !== "string") {
return dataWithError(
"Invalid or missing p_app_id value",
"Helaas, er is wat misgegaan. Probeer het later opnieuw of neem contact op met ondersteuning.",
)
}
await updateFertilizerApplication(
fdm,
session.principal_id,
p_app_id,
p_id,
p_app_amount,
p_app_method,
p_app_date,
)
return dataWithSuccess("Data edited successfully", {
message: "Bemesting is gewijzigd",
})
}
🤖 Prompt for AI Agents
In
fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx
around lines 216 to 245, there are two typos in the user-facing messages: change
"Helaas, er is wat misggegaan. Probeer het later opnieuw of neem contact op met
ondersteuning." to "Helaas, er is wat misgegaan. Probeer het later opnieuw of
neem contact op met ondersteuning." (fix double g to single g) and change the
success message "Date edited successfully" to "Data edited successfully" so it
matches the codebase pattern; keep all other logic unchanged.


if (request.method === "DELETE") {
const formData = await request.formData()
const p_app_id = formData.get("p_app_id")

if (!p_app_id || typeof p_app_id !== "string") {
return dataWithError(
"Invalid or missing p_app_id value",
"Oops! Something went wrong. Please try again later.",
"Helaas, er is wat misgegaan. Probeer het later opnieuw of neem contact op met ondersteuning.",
)
}

Expand Down
Loading