diff --git a/.changeset/free-onions-rest.md b/.changeset/free-onions-rest.md new file mode 100644 index 000000000..fd0228b6d --- /dev/null +++ b/.changeset/free-onions-rest.md @@ -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. diff --git a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx index f6316fba5..e3f6c25e7 100644 --- a/fdm-app/app/components/blocks/fertilizer-applications/card.tsx +++ b/fdm-app/app/components/blocks/fertilizer-applications/card.tsx @@ -155,6 +155,8 @@ export function FertilizerApplicationCard({ const params = useParams() const navigation = useNavigation() const [isDialogOpen, setIsDialogOpen] = useState(false) + const [editedFertilizerApplication, setEditedFertilizerApplication] = + useState() const previousNavigationState = useRef(navigation.state) const b_id_or_b_lu_catalogue = params.b_lu_catalogue || params.b_id @@ -165,12 +167,18 @@ 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 @@ -178,14 +186,51 @@ export function FertilizerApplicationCard({ 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) @@ -197,6 +242,10 @@ export function FertilizerApplicationCard({ ) } + if (!state) { + setEditedFertilizerApplication(undefined) + } + setIsDialogOpen(state) } @@ -206,8 +255,8 @@ export function FertilizerApplicationCard({

Bemesting

- 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

- Bemesting toevoegen + {editedFertilizerApplication + ? "Bemesting wijzigen" + : "Bemesting toevoegen"} - Voeg een nieuwe bemestingstoepassing toe aan het - perceel. + {editedFertilizerApplication + ? "Wijzig een bemestingtoepassing aan het percel." + : "Voeg een nieuwe bemestingstoepassing toe aan het perceel."} @@ -244,7 +297,7 @@ export function FertilizerApplicationCard({ {fertilizerApplications.length > 0 ? ( fertilizerApplications.map((application) => (
@@ -274,6 +327,17 @@ export function FertilizerApplicationCard({ )}

+
+ +
+ ) : fertilizerApplication ? ( + "Opslaan" ) : ( "Voeg toe" )} diff --git a/fdm-app/app/components/blocks/fertilizer-applications/formschema.tsx b/fdm-app/app/components/blocks/fertilizer-applications/formschema.tsx index b75e939d9..5aa24ced7 100644 --- a/fdm-app/app/components/blocks/fertilizer-applications/formschema.tsx +++ b/fdm-app/app/components/blocks/fertilizer-applications/formschema.tsx @@ -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 & { + p_app_id?: string | undefined +} diff --git a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx index f97d614da..c33282a1e 100644 --- a/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx +++ b/fdm-app/app/routes/farm.$b_id_farm.$calendar.field.$b_id.fertilizer._index.tsx @@ -6,6 +6,7 @@ import { getFertilizers, getField, removeFertilizerApplication, + updateFertilizerApplication, } from "@svenvw/fdm-core" import { type ActionFunctionArgs, @@ -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" @@ -209,6 +213,37 @@ 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", + }) + } + if (request.method === "DELETE") { const formData = await request.formData() const p_app_id = formData.get("p_app_id") @@ -216,7 +251,7 @@ export async function action({ request, params }: ActionFunctionArgs) { 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.", ) } diff --git a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue._index.tsx b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue._index.tsx index 99817a2c7..cc1417153 100644 --- a/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue._index.tsx +++ b/fdm-app/app/routes/farm.create.$b_id_farm.$calendar.fertilizers.$b_lu_catalogue._index.tsx @@ -5,6 +5,7 @@ import { getFertilizerParametersDescription, getFertilizers, removeFertilizerApplication, + updateFertilizerApplication, } from "@svenvw/fdm-core" import { type ActionFunctionArgs, @@ -15,7 +16,10 @@ import { } from "react-router" import { 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" @@ -67,6 +71,15 @@ export async function loader({ request, params }: LoaderFunctionArgs) { session.principal_id, b_id_farm, ) + + // Map fertilizer catalogue ids to their farm fertilizer acquiring ids + const fertilizerAcquiringIds: Record = {} + for (const fertilizer of fertilizers) { + if (fertilizer.p_id_catalogue) { + fertilizerAcquiringIds[fertilizer.p_id_catalogue] = fertilizer.p_id + } + } + const fertilizerParameterDescription = getFertilizerParametersDescription() const applicationMethods = fertilizerParameterDescription.find( (x: { parameter: string }) => x.parameter === "p_app_method_options", @@ -126,7 +139,13 @@ export async function loader({ request, params }: LoaderFunctionArgs) { existingApplication.p_app_ids.push(app.p_app_id) } else { // If it's a new application, add it to the accumulator with a new p_app_ids array. - accumulator.push({ ...app, p_app_ids: [app.p_app_id] }) + accumulator.push({ + ...app, + p_id: + fertilizerAcquiringIds[app.p_id_catalogue] ?? + app.p_id_catalogue, + p_app_ids: [app.p_app_id], + }) } }) @@ -229,6 +248,37 @@ export async function action({ request, params }: ActionFunctionArgs) { { message: "Bemesting is toegevoegd! 🎉" }, ) } + if (request.method === "PUT") { + const formValues = await extractFormValuesFromRequest( + request, + FormSchemaModify, + ) + const rawAppIds = formValues.p_app_id + + if (!rawAppIds || typeof rawAppIds !== "string") { + throw new Error("invalid: p_app_id") + } + + const p_app_ids = rawAppIds.split(",") + + const { p_id, p_app_amount, p_app_date, p_app_method } = formValues + + await Promise.all( + p_app_ids.map((p_app_id: string) => + updateFertilizerApplication( + fdm, + session.principal_id, + p_app_id, + p_id, + p_app_amount, + p_app_method, + p_app_date, + ), + ), + ) + + return dataWithSuccess({}, { message: "Bemesting is gewijzigd" }) + } if (request.method === "DELETE") { const formData = await request.formData() const rawAppIds = formData.get("p_app_id") diff --git a/fdm-app/app/store/field-fertilizer-form.tsx b/fdm-app/app/store/field-fertilizer-form.tsx index f716042cc..75dedf5f7 100644 --- a/fdm-app/app/store/field-fertilizer-form.tsx +++ b/fdm-app/app/store/field-fertilizer-form.tsx @@ -1,9 +1,7 @@ -import type { z } from "zod" import { create } from "zustand" import { createJSONStorage, persist } from "zustand/middleware" -import type { FormSchema } from "~/components/blocks/fertilizer-applications/formschema" +import type { FieldFertilizerFormValues } from "~/components/blocks/fertilizer-applications/formschema" -type FieldFertilizerFormValues = z.infer interface FieldFertilizerFormStore { db: Record> save(