Repository: boxwise/boxtribute
Pull request #2171: Edit product standard instantiation
Author: login: fhenrich33
Created: 2025-05-21T09:16:27Z | Merged: 2025-05-21T15:15:11Z

![firefox_CYFCU28BBs](https://github.com/user-attachments/assets/a68c677c-c5ab-4785-b763-ecc55ff31a01)



Diff:
diff --git a/front/src/App.tsx b/front/src/App.tsx
index ffa8da66e3..2f4462e2fa 100644
--- a/front/src/App.tsx
+++ b/front/src/App.tsx
@@ -28,6 +28,7 @@ import { useAtomValue } from "jotai";
 import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
 import CreateCustomProductView from "views/CreateCustomProduct/CreateCustomProductView";
 import EditCustomProductView from "views/EditCustomProduct/EditCustomProductView";
+import EditStandardProductView from "views/EditStandardProduct/EditStandardProductView";
 
 type ProtectedRouteProps = {
   component: ReactElement;
@@ -306,15 +307,6 @@ function App() {
               }
             />
             <Route path="edit">
-              <Route
-                element={
-                  <Protected
-                    component={<EnableStandardProductView />}
-                    redirectPath={prevLocation}
-                    requiredAbps={["manage_products"]}
-                  />
-                }
-              />
               <Route
                 path=":customProductId"
                 element={
@@ -325,6 +317,18 @@ function App() {
                   />
                 }
               />
+              <Route path="standard">
+                <Route
+                  path=":standardProductId"
+                  element={
+                    <Protected
+                      component={<EditStandardProductView />}
+                      redirectPath={prevLocation}
+                      requiredAbps={["manage_products"]}
+                    />
+                  }
+                />
+              </Route>
             </Route>
           </Route>
         </Route>
diff --git a/front/src/components/Form/SelectField.tsx b/front/src/components/Form/SelectField.tsx
index 22faa19b79..cf4d40949c 100644
--- a/front/src/components/Form/SelectField.tsx
+++ b/front/src/components/Form/SelectField.tsx
@@ -22,6 +22,8 @@ export interface ISelectFieldProps {
   isRequired?: boolean;
   showLabel?: boolean;
   showError?: boolean;
+  isDisabled?: boolean;
+  isReadOnly?: boolean;
   onChangeProp?: ((event) => void) | undefined;
   formatOptionLabel?: (
     data: IDropdownOption,
@@ -42,6 +44,8 @@ function SelectField({
   errors,
   control,
   isMulti = false,
+  isDisabled = false,
+  isReadOnly = false,
   isRequired = true,
   onChangeProp = undefined,
   formatOptionLabel,
@@ -61,6 +65,8 @@ function SelectField({
           <Select
             name={name}
             ref={ref}
+            isReadOnly={isReadOnly}
+            isDisabled={isDisabled}
             onChange={
               onChangeProp
                 ? (event) => {
diff --git a/front/src/queries/queries.ts b/front/src/queries/queries.ts
index bb928a85bb..cc967307d5 100644
--- a/front/src/queries/queries.ts
+++ b/front/src/queries/queries.ts
@@ -2,6 +2,7 @@ import { graphql } from "../../../graphql/graphql";
 import {
   BOX_BASIC_FIELDS_FRAGMENT,
   PRODUCT_BASIC_FIELDS_FRAGMENT,
+  STANDARD_PRODUCT_BASIC_FIELDS_FRAGMENT,
 } from "../../../graphql/fragments";
 import {
   PRODUCT_FIELDS_FRAGMENT,
@@ -240,6 +241,29 @@ export const MULTI_BOX_ACTION_OPTIONS_FOR_LOCATIONS_TAGS_AND_SHIPMENTS_QUERY = g
   [LOCATION_BASIC_FIELDS_FRAGMENT, TAG_BASIC_FIELDS_FRAGMENT, BASE_ORG_FIELDS_FRAGMENT],
 );
 
+export const STANDARD_PRODUCT_QUERY = graphql(
+  `
+    query StandardProductQuery($baseId: ID!) {
+      standardProducts(baseId: $baseId) {
+        __typename
+        ... on StandardProductPage {
+          elements {
+            ...StandardProductBasicFields
+            instantiation {
+              id
+              price
+              comment
+              inShop
+              deletedOn
+            }
+          }
+        }
+      }
+    }
+  `,
+  [STANDARD_PRODUCT_BASIC_FIELDS_FRAGMENT],
+);
+
 export const CUSTOM_PRODUCT_FORM_OPTIONS_QUERY = graphql(
   `
     query CustomProductFormOptions {
diff --git a/front/src/queries/types.ts b/front/src/queries/types.ts
index 200b70a36d..baea6c8a86 100644
--- a/front/src/queries/types.ts
+++ b/front/src/queries/types.ts
@@ -3,6 +3,7 @@ import {
   BOX_QUERY,
   BOX_BY_LABEL_IDENTIFIER_AND_ALL_SHIPMENTS_QUERY,
   CUSTOM_PRODUCT_FORM_OPTIONS_QUERY,
+  STANDARD_PRODUCT_QUERY,
 } from "./queries";
 import { UPDATE_BOX_MUTATION } from "views/Box/BoxView";
 import { BOXES_FOR_BOXESVIEW_QUERY } from "views/Boxes/BoxesView";
@@ -45,3 +46,4 @@ export type StandardProductsforProductsViewVariables = VariablesOf<
 export type ProductsQuery = ResultOf<typeof PRODUCTS_QUERY>;
 
 export type CustomProductFormQueryResult = ResultOf<typeof CUSTOM_PRODUCT_FORM_OPTIONS_QUERY>;
+export type StandardProductQueryResultType = ResultOf<typeof STANDARD_PRODUCT_QUERY>;
diff --git a/front/src/views/EditCustomProduct/EditCustomProductView.tsx b/front/src/views/EditCustomProduct/EditCustomProductView.tsx
index 2c77bba390..26e22dd0b0 100644
--- a/front/src/views/EditCustomProduct/EditCustomProductView.tsx
+++ b/front/src/views/EditCustomProduct/EditCustomProductView.tsx
@@ -65,6 +65,11 @@ const EDIT_CUSTOM_PRODUCT_MUTATION = graphql(
           price
           inShop
           comment
+          lastModifiedOn
+          lastModifiedBy {
+            id
+            name
+          }
         }
       }
     }
diff --git a/front/src/views/EditCustomProduct/components/EditCustomProductForm.tsx b/front/src/views/EditCustomProduct/components/EditCustomProductForm.tsx
index d170b26384..5ea0cbe545 100644
--- a/front/src/views/EditCustomProduct/components/EditCustomProductForm.tsx
+++ b/front/src/views/EditCustomProduct/components/EditCustomProductForm.tsx
@@ -7,7 +7,6 @@ import {
   HStack,
   Input,
   Stack,
-  // Switch,
   Text,
   VStack,
 } from "@chakra-ui/react";
@@ -34,10 +33,7 @@ const EditCustomProductFormSchema = z.object({
   category: SingleSelectOptionSchema.optional(),
   gender: SingleSelectOptionSchema.optional(),
   sizeRange: SingleSelectOptionSchema.optional(),
-  comment: z
-    .string()
-    .optional()
-    .transform((value) => (value === "" ? undefined : value)),
+  comment: z.string().optional(),
   inShop: z.boolean().optional(),
   price: z
     .number({
@@ -84,7 +80,7 @@ function EditCustomProductForm({
       <Box border="2px" mb={4} p={2}>
         <HStack borderBottom="2px" pb={2} px={2}>
           <Text fontWeight="bold" fontSize="md">
-            PRODUCT SETUP
+            PRODUCT DETAILS
           </Text>
         </HStack>
         <VStack spacing={4} p={2} mt={2}>
@@ -139,7 +135,6 @@ function EditCustomProductForm({
             errors={errors}
             control={control}
           />
-
           <FormControl>
             <FormLabel htmlFor="comment">Description</FormLabel>
             <Input
diff --git a/front/src/views/EditStandardProduct/EditStandardProductView.tsx b/front/src/views/EditStandardProduct/EditStandardProductView.tsx
new file mode 100644
index 0000000000..16923f0d64
--- /dev/null
+++ b/front/src/views/EditStandardProduct/EditStandardProductView.tsx
@@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          lastModifiedOn
+          lastModifiedBy {
+            id
+            name
+          }
+        }
+      }
+    }
+  `,
+  [],
+);
+
+function EditStandardProductFormContainer() {
+  const navigate = useNavigate();
+  const baseId = useAtomValue(selectedBaseIdAtom);
+  const requestedStandardProductId = useParams<{ standardProductId: string }>().standardProductId;
+  const { createToast } = useNotification();
+  const { triggerError } = useErrorHandling();
+  const { data: standardProductsRawData, error } = useSuspenseQuery(STANDARD_PRODUCT_QUERY, {
+    variables: { baseId },
+  });
+
+  const [editStandardProduct, { loading: isEditStandardProductLoading }] = useMutation(
+    EDIT_STANDARD_PRODUCT_MUTATION,
+  );
+
+  const onSubmit = useCallback(
+    (editStandardProductFormOutput: EditStandardProductFormOutput) => {
+      editStandardProduct({
+        variables: {
+          id: editStandardProductFormOutput.instantiation.value,
+          comment: editStandardProductFormOutput.comment,
+          price: editStandardProductFormOutput.price,
+          inShop: editStandardProductFormOutput.inShop,
+        },
+      })
+        .then(({ data }) => {
+          const result = data?.editStandardProductInstantiation;
+          if (!result) return;
+
+          switch (result.__typename) {
+            case "Product":
+              createToast({
+                message: `The ASSORT standard product was successfully edited.`,
+              });
+              navigate(`../../../`);
+
+              break;
+            case "InsufficientPermissionError":
+              triggerError({
+                message: "You don't have permission to edit this ASSORT standard product!",
+              });
+              break;
+            case "InvalidPriceError":
+              triggerError({
+                message: "Price must be a positive integer number.",
+              });
+              break;
+            default:
+              triggerError({
+                message: "Could not edit this ASSORT standard product! Try again?",
+              });
+              break;
+          }
+        })
+        .catch(() => {
+          // Handle network or other errors
+          triggerError({
+            message: "Could not edit this ASSORT standard product! Try again?",
+          });
+        });
+    },
+    [editStandardProduct, createToast, navigate, triggerError],
+  );
+
+  // If Apollo encountered an error (like network error), throw it
+  if (error) throw error;
+
+  // Otherwise, data is now loaded (Suspense has ended), but there could be errors returned in the data
+  if (
+    !standardProductsRawData ||
+    standardProductsRawData.standardProducts?.__typename !== "StandardProductPage"
+  ) {
+    throw new Error(StandardProductQueryErrorText);
+  }
+
+  const standardProductData = standardProductRawToFormDataTransformer(standardProductsRawData);
+
+  const defaultValues = findDefaultValues(standardProductData, requestedStandardProductId);
+
+  return (
+    <EditStandardProductForm
+      standardProductData={standardProductData}
+      defaultValues={defaultValues}
+      onSubmit={onSubmit}
+      isLoading={isEditStandardProductLoading}
+      showAlert={false}
+      key={requestedStandardProductId}
+    />
+  );
+}
+
+function EditStandardProductView() {
+  return (
+    <>
+      <MobileBreadcrumbButton label="Back to Manage Products" linkPath="../../../" />
+      <Center>
+        <Box w={["100%", "100%", "60%", "40%"]}>
+          <Heading fontWeight="bold" mb={8} as="h1">
+            Edit Standard Product
+          </Heading>
+          <ErrorBoundary
+            fallback={({ error }) => (
+              <AlertWithoutAction
+                type="error"
+                alertText={error?.toString() || StandardProductQueryErrorText}
+              />
+            )}
+          >
+            <Suspense fallback={<FormSkeleton />}>
+              <EditStandardProductFormContainer />
+            </Suspense>
+          </ErrorBoundary>
+        </Box>
+      </Center>
+    </>
+  );
+}
+
+export default EditStandardProductView;
diff --git a/front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx b/front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx
new file mode 100644
index 0000000000..6f4e9180ff
--- /dev/null
+++ b/front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx
@@ -0,0 +1,228 @@
+import { useEffect, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+  HStack,
+  Stack,
+  Button,
+  Box,
+  Text,
+  Skeleton,
+  Select,
+  FormControl,
+  FormLabel,
+  VStack,
+  Input,
+} from "@chakra-ui/react";
+import { useAtomValue } from "jotai";
+
+import { AlertWithoutAction } from "components/Alerts";
+import { NewNumberField } from "components/Form/NumberField";
+import SelectField from "components/Form/SelectField";
+import SwitchField from "components/Form/SwitchField";
+import { selectedBaseAtom } from "stores/globalPreferenceStore";
+import { StandardProductFormSchema } from "views/EnableStandardProduct/components/transformer";
+
+export type EditStandardProductFormInput = z.input<typeof StandardProductFormSchema>;
+export type EditStandardProductFormOutput = z.output<typeof StandardProductFormSchema>;
+
+export type EditStandardProductFormProps = {
+  showAlert: boolean;
+  isLoading: boolean;
+  standardProductData: EditStandardProductFormInput[];
+  defaultValues?: EditStandardProductFormInput;
+  onSubmit: (editStandardProductFormData: EditStandardProductFormOutput) => void;
+};
+
+function EditStandardProductForm({
+  showAlert,
+  isLoading,
+  standardProductData,
+  defaultValues,
+  onSubmit,
+}: EditStandardProductFormProps) {
+  const navigate = useNavigate();
+  const selectedBase = useAtomValue(selectedBaseAtom);
+  const baseName = selectedBase?.name;
+
+  const {
+    handleSubmit,
+    control,
+    register,
+    watch,
+    formState: { errors },
+  } = useForm<EditStandardProductFormInput>({
+    resolver: zodResolver(StandardProductFormSchema),
+    ...(defaultValues ? { defaultValues } : {}),
+  });
+
+  const selectedStandardProduct = watch("standardProduct");
+
+  useEffect(() => {
+    if (defaultValues && selectedStandardProduct.value !== defaultValues.standardProduct.value) {
+      navigate(`../${selectedStandardProduct.value}`);
+    }
+    if (!defaultValues && !!selectedStandardProduct) {
+      navigate(`${selectedStandardProduct.value}`);
+    }
+  }, [defaultValues, navigate, selectedStandardProduct]);
+
+  const sortedProductOptions = useMemo(() => {
+    return [...standardProductData]
+      .sort((a, b) => a.standardProduct.label.localeCompare(b.standardProduct.label))
+      .map((data) => ({
+        label: data.standardProduct.label,
+        value: data.standardProduct.value,
+        data: { gender: data.gender },
+      }));
+  }, [standardProductData]);
+
+  return (
+    <>
+      {showAlert && (
+        <AlertWithoutAction
+          type="info"
+          closeable={true}
+          alertText=" For ASSORT standard products, only the product description, and free shop settings can be edited."
+          mb={2}
+        />
+      )}
+      <form onSubmit={handleSubmit(onSubmit)}>
+        <Box border="2px" mb={4} p={2}>
+          <HStack borderBottom="2px" pb={2} px={2}>
+            <Text fontWeight="bold" fontSize="md">
+              {baseName ? baseName?.toUpperCase() : <Skeleton height={6} width={20} />}
+            </Text>
+            <Text fontWeight="bold" fontSize="md">
+              PRODUCT DETAILS
+            </Text>
+          </HStack>
+          <VStack>
+            <VStack p={2} w="full" bg="gray.100" borderRadius={10} mt={2}>
+              <SelectField
+                fieldId="standardProduct"
+                isReadOnly
+                fieldLabel="Name"
+                placeholder="Please select a standard product."
+                options={sortedProductOptions}
+                formatOptionLabel={(option, { context }) => {
+                  if (context === "menu") {
+                    // In dropdown menu: show name and gender
+                    return (
+                      <HStack>
+                        <Text>{option.label}</Text>
+                        {option.data && (option.data as { gender?: string }).gender !== "none" && (
+                          <Text color="gray.500" fontSize="sm">
+                            ({(option.data as { gender?: string }).gender})
+                          </Text>
+                        )}
+                      </HStack>
+                    );
+                  }
+                  // When selected: show only name
+                  return option.label;
+                }}
+                errors={errors}
+                control={control}
+              />
+              <FormControl>
+                <FormLabel>Category</FormLabel>
+                <Select
+                  value={defaultValues?.category?.value}
+                  isReadOnly
+                  isDisabled
+                  _disabled={{ color: "black" }}
+                  placeholder="Please select a standard product."
+                >
+                  <option value={defaultValues?.category?.value}>
+                    {defaultValues?.category?.label}
+                  </option>
+                </Select>
+              </FormControl>
+              <FormControl>
+                <FormLabel>Gender</FormLabel>
+                <Select
+                  value={defaultValues?.gender}
+                  isReadOnly
+                  isDisabled
+                  _disabled={{ color: "black" }}
+                  placeholder="Please select a standard product."
+                >
+                  <option value={defaultValues?.gender}>
+                    {defaultValues?.gender === "none" ? "-" : defaultValues?.gender}
+                  </option>
+                </Select>
+              </FormControl>
+              <FormControl>
+                <FormLabel>Size Range</FormLabel>
+                <Select
+                  value={defaultValues?.sizeRange?.value}
+                  isReadOnly
+                  isDisabled
+                  _disabled={{ color: "black" }}
+                  placeholder="Please select a standard product."
+                >
+                  <option value={defaultValues?.sizeRange?.value}>
+                    {defaultValues?.sizeRange?.label}
+                  </option>
+                </Select>
+              </FormControl>
+            </VStack>
+            <FormControl p={2}>
+              <FormLabel htmlFor="comment">Description</FormLabel>
+              <Input
+                borderColor="black"
+                border="2px"
+                borderRadius={0}
+                _focus={{ borderColor: "blue.500" }}
+                _hover={{ borderColor: "gray.300" }}
+                size="lg"
+                type="string"
+                {...register("comment")}
+              />
+            </FormControl>
+          </VStack>
+        </Box>
+        <Box border="2px" mb={4} p={2}>
+          <Box borderBottom="2px" pb={2} px={2} mb={2}>
+            <Text fontWeight="bold" fontSize="md">
+              FREE SHOP SETTINGS
+            </Text>
+          </Box>
+          <SwitchField fieldId="inShop" fieldLabel="Always Show in Stockroom?" control={control} />
+          <NewNumberField
+            fieldId="price"
+            fieldLabel="Token Price"
+            errors={errors}
+            control={control}
+            p={2}
+          />
+        </Box>
+        <Stack spacing={2} my={4}>
+          <Button
+            isLoading={isLoading}
+            disabled={isLoading}
+            type="submit"
+            w="full"
+            variant="submit"
+          >
+            Edit Product
+          </Button>
+          <Button
+            size="md"
+            type="button"
+            w="full"
+            variant="cancel"
+            onClick={() => navigate("../../../")}
+          >
+            Nevermind
+          </Button>
+        </Stack>
+      </form>
+    </>
+  );
+}
+
+export default EditStandardProductForm;
diff --git a/front/src/views/EnableStandardProduct/EnableStandardProductView.tsx b/front/src/views/EnableStandardProduct/EnableStandardProductView.tsx
index 4711ee66fc..1a77dceb1c 100644
--- a/front/src/views/EnableStandardProduct/EnableStandardProductView.tsx
+++ b/front/src/views/EnableStandardProduct/EnableStandardProductView.tsx
@@ -1,93 +1,67 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
 import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { graphql } from "../../../../graphql/graphql";
 import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
-import { graphql, ResultOf } from "../../../../graphql/graphql";
-import { STANDARD_PRODUCT_BASIC_FIELDS_FRAGMENT } from "../../../../graphql/fragments";
 import EnableStandardProductForm, {
-  IEnableStandardProductFormOutput,
+  EnableStandardProductFormOutput,
 } from "./components/EnableStandardProductForm";
-import { ErrorBoundary } from "@sentry/react";
 import { AlertWithoutAction } from "components/Alerts";
 import { FormSkeleton } from "components/Skeletons";
-import { Suspense, useCallback } from "react";
-import { useNavigate, useParams } from "react-router-dom";
-import { useMutation, useSuspenseQuery } from "@apollo/client";
 import {
   findDefaultValues,
   standardProductRawToFormDataTransformer,
 } from "./components/transformer";
-import { useAtomValue } from "jotai";
 import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
 import { useNotification } from "hooks/useNotification";
 import { useErrorHandling } from "hooks/useErrorHandling";
 import { STANDARD_PRODUCTS_FOR_PRODUCTVIEW_QUERY } from "views/Products/components/StandardProductsContainer";
 import { PRODUCTS_QUERY } from "views/Products/components/ProductsContainer";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
 
-export const ENABLE_STANDARD_PRODUCT_QUERY = graphql(
-  `
-    query EnableStandardProductQuery($baseId: ID!) {
-      standardProducts(baseId: $baseId) {
-        __typename
-        ... on StandardProductPage {
-          elements {
-            ...StandardProductBasicFields
-            instantiation {
-              id
-              price
-              comment
-              inShop
-              deletedOn
-            }
-          }
-        }
-      }
-    }
-  `,
-  [STANDARD_PRODUCT_BASIC_FIELDS_FRAGMENT],
-);
-
-export type IEnableStandardProductQueryResultType = ResultOf<typeof ENABLE_STANDARD_PRODUCT_QUERY>;
 export const enableStandardProductQueryErrorText =
   "Could not fetch ASSORT standard data! Please try reloading the page.";
 
-export const ENABLE_STANDARD_PRODUCT_MUTATION = graphql(
-  `
-    mutation EnableStandardProductMutation(
-      $baseId: Int!
-      $standardProductId: Int!
-      $comment: String
-      $inShop: Boolean
-      $price: Int
+export const ENABLE_STANDARD_PRODUCT_MUTATION = graphql(`
+  mutation EnableStandardProductMutation(
+    $baseId: Int!
+    $standardProductId: Int!
+    $comment: String
+    $inShop: Boolean
+    $price: Int
+  ) {
+    enableStandardProduct(
+      enableInput: {
+        baseId: $baseId
+        standardProductId: $standardProductId
+        comment: $comment
+        inShop: $inShop
+        price: $price
+      }
     ) {
-      enableStandardProduct(
-        enableInput: {
-          baseId: $baseId
-          standardProductId: $standardProductId
-          comment: $comment
-          inShop: $inShop
-          price: $price
+      __typename
+      ... on Product {
+        id
+        type
+        standardProduct {
+          id
         }
-      ) {
-        __typename
-        ... on Product {
+        comment
+        inShop
+        price
+        createdOn
+        createdBy {
           id
-          type
-          standardProduct {
-            id
-          }
-          comment
-          inShop
-          price
-          createdOn
-          createdBy {
-            id
-            name
-          }
+          name
         }
       }
     }
-  `,
-  [],
-);
+  }
+`);
 
 function EnableStandardProductFormContainer() {
   const navigate = useNavigate();
@@ -95,7 +69,7 @@ function EnableStandardProductFormContainer() {
   const requestedStandardProductId = useParams<{ standardProductId: string }>().standardProductId;
   const { createToast } = useNotification();
   const { triggerError } = useErrorHandling();
-  const { data: standardProductsRawData, error } = useSuspenseQuery(ENABLE_STANDARD_PRODUCT_QUERY, {
+  const { data: standardProductsRawData, error } = useSuspenseQuery(STANDARD_PRODUCT_QUERY, {
     variables: { baseId },
   });
 
@@ -104,7 +78,7 @@ function EnableStandardProductFormContainer() {
   );
 
   const onSubmit = useCallback(
-    (enableStandardProductFormOutput: IEnableStandardProductFormOutput) => {
+    (enableStandardProductFormOutput: EnableStandardProductFormOutput) => {
       enableStandardProduct({
         variables: {
           standardProductId: parseInt(enableStandardProductFormOutput.standardProduct.value, 10),
diff --git a/front/src/views/EnableStandardProduct/components/EnableStandardProductForm.tsx b/front/src/views/EnableStandardProduct/components/EnableStandardProductForm.tsx
index 86fc95569d..fc26e24fdc 100644
--- a/front/src/views/EnableStandardProduct/components/EnableStandardProductForm.tsx
+++ b/front/src/views/EnableStandardProduct/components/EnableStandardProductForm.tsx
@@ -1,3 +1,7 @@
+import { useEffect, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { useAtomValue } from "jotai";
 import {
   HStack,
   Stack,
@@ -12,55 +16,25 @@ import {
   Input,
   Switch,
 } from "@chakra-ui/react";
+import { z } from "zod";
 import { zodResolver } from "@hookform/resolvers/zod";
+
 import { AlertWithoutAction } from "components/Alerts";
 import { NewNumberField } from "components/Form/NumberField";
 import SelectField from "components/Form/SelectField";
 import SwitchField from "components/Form/SwitchField";
-import { useAtomValue } from "jotai";
-import { useEffect, useMemo } from "react";
-import { useForm } from "react-hook-form";
-import { useNavigate } from "react-router-dom";
 import { selectedBaseAtom } from "stores/globalPreferenceStore";
-import { z } from "zod";
-
-const SingleSelectOptionSchema = z.object({
-  label: z.string(),
-  value: z.string(),
-});
-
-export const EnableStandardProductFormSchema = z.object({
-  // see BoxEdit.tsx how to validate a select field
-  standardProduct: z.object(
-    { value: z.string(), label: z.string() },
-    { required_error: "Please select a standard product." },
-  ),
-  category: SingleSelectOptionSchema.optional(),
-  gender: z.string().optional(),
-  sizeRange: SingleSelectOptionSchema.optional(),
-  comment: z
-    .string()
-    .optional()
-    .transform((value) => (value === "" ? undefined : value)),
-  inShop: z.boolean().optional(),
-  price: z
-    .number({
-      invalid_type_error: "Please enter a positive integer number.",
-    })
-    .int()
-    .nonnegative()
-    .optional(),
-});
+import { StandardProductFormSchema } from "./transformer";
 
-export type IEnableStandardProductFormInput = z.input<typeof EnableStandardProductFormSchema>;
-export type IEnableStandardProductFormOutput = z.output<typeof EnableStandardProductFormSchema>;
+export type EnableStandardProductFormInput = z.input<typeof StandardProductFormSchema>;
+export type EnableStandardProductFormOutput = z.output<typeof StandardProductFormSchema>;
 
 export type IEnableStandardProductFormProps = {
   showAlert: boolean;
   isLoading: boolean;
-  standardProductData: IEnableStandardProductFormInput[];
-  defaultValues?: IEnableStandardProductFormInput;
-  onSubmit: (enableStandardProductFormData: IEnableStandardProductFormOutput) => void;
+  standardProductData: EnableStandardProductFormInput[];
+  defaultValues?: EnableStandardProductFormInput;
+  onSubmit: (enableStandardProductFormData: EnableStandardProductFormOutput) => void;
 };
 
 function EnableStandardProductForm({
@@ -80,8 +54,8 @@ function EnableStandardProductForm({
     register,
     watch,
     formState: { errors },
-  } = useForm<IEnableStandardProductFormInput>({
-    resolver: zodResolver(EnableStandardProductFormSchema),
+  } = useForm<EnableStandardProductFormInput>({
+    resolver: zodResolver(StandardProductFormSchema),
     ...(defaultValues ? { defaultValues } : {}),
   });
 
diff --git a/front/src/views/EnableStandardProduct/components/transformer.ts b/front/src/views/EnableStandardProduct/components/transformer.ts
index d94733632e..8486c75a7d 100644
--- a/front/src/views/EnableStandardProduct/components/transformer.ts
+++ b/front/src/views/EnableStandardProduct/components/transformer.ts
@@ -1,17 +1,47 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z.string().optional(),
+  inShop: z.boolean().optional(),
+  price: z
+    .number({
+      invalid_type_error: "Please enter a positive integer number.",
+    })
+    .int()
+    .nonnegative()
+    .optional(),
+});
 
 export const standardProductRawToFormDataTransformer = (
-  standardProductRawData: IEnableStandardProductQueryResultType,
+  standardProductRawData: StandardProductQueryResultType,
 ) => {
   if (standardProductRawData.standardProducts?.__typename === "StandardProductPage") {
     return standardProductRawData.standardProducts.elements.map(
       ({ id, name, category, gender, sizeRange, instantiation }) => {
         const nonDeletedInstantiation = instantiation?.deletedOn ? undefined : instantiation;
         return {
+          instantiation: {
+            label: name,
+            value: instantiation?.id || "",
+          },
           standardProduct: {
             label: name,
             value: id,
@@ -28,7 +58,7 @@ export const standardProductRawToFormDataTransformer = (
           comment: nonDeletedInstantiation?.comment ? nonDeletedInstantiation.comment : undefined,
           inShop: nonDeletedInstantiation?.inShop ? nonDeletedInstantiation.inShop : undefined,
           price: nonDeletedInstantiation?.price ? nonDeletedInstantiation.price : undefined,
-        } satisfies IEnableStandardProductFormInput;
+        } satisfies EnableStandardProductFormInput;
       },
     );
   } else {
@@ -37,19 +67,17 @@ export const standardProductRawToFormDataTransformer = (
 };
 
 export const findDefaultValues = (
-  standardProductData: IEnableStandardProductFormInput[],
+  standardProductData: EnableStandardProductFormInput[],
   requestedStandardProductId: string | undefined,
 ) => {
-  if (!requestedStandardProductId) {
-    return undefined;
-  }
+  if (!requestedStandardProductId) return undefined;
+
   const defaultValues = standardProductData.find(
     (standardProduct) => standardProduct.standardProduct.value === requestedStandardProductId,
   );
 
   // Handle the case where the requested standard product is not found
-  if (!defaultValues) {
-    throw new Error("Requested ASSORT standard product not found!");
-  }
+  if (!defaultValues) throw new Error("Requested ASSORT standard product not found!");
+
   return defaultValues;
 };
diff --git a/front/src/views/Products/components/ProductsContainer.tsx b/front/src/views/Products/components/ProductsContainer.tsx
index 149c284560..aafdbcede7 100644
--- a/front/src/views/Products/components/ProductsContainer.tsx
+++ b/front/src/views/Products/components/ProductsContainer.tsx
@@ -23,6 +23,9 @@ export const PRODUCTS_QUERY = graphql(
         totalCount
         elements {
           ...ProductBasicFields
+          standardProduct {
+            id
+          }
           sizeRange {
             ...SizeRangeFields
           }
@@ -65,16 +68,13 @@ function ProductsContainer() {
   });
 
   const onRowClick = (productId: string, isStandard = false) => {
-    // TODO: handle standard product edit
     const path = isStandard ? "edit/standard" : "edit";
-    if (!isStandard) navigate(`/bases/${baseId}/products/${path}/${productId}`);
+    navigate(`/bases/${baseId}/products/${path}/${productId}`);
   };
 
   // fetch Products data
   const { data: productsRawData, error } = useSuspenseQuery(PRODUCTS_QUERY, {
-    variables: {
-      baseId,
-    },
+    variables: { baseId },
   });
 
   const availableColumns: Column<ProductRow>[] = useMemo(
diff --git a/front/src/views/Products/components/ProductsTable.tsx b/front/src/views/Products/components/ProductsTable.tsx
index 9f41006caa..3fd77201d5 100644
--- a/front/src/views/Products/components/ProductsTable.tsx
+++ b/front/src/views/Products/components/ProductsTable.tsx
@@ -104,9 +104,15 @@ function ProductsTable({ tableConfig, tableData, columns, onRowClick }: ProductT
               <Tr
                 {...row.getRowProps()}
                 key={row.values.id}
-                onClick={() => onRowClick(row.original.id, row.original.isStandard)}
-                // TODO: handle standard product edit
-                cursor={row.original.isStandard ? "inherit" : "pointer"}
+                onClick={() =>
+                  onRowClick(
+                    row.original.isStandard
+                      ? row.original.standardInstantiationId
+                      : row.original.id,
+                    row.original.isStandard,
+                  )
+                }
+                cursor="pointer"
               >
                 {row.cells.map((cell) => (
                   <Td {...cell.getCellProps()} key={`${row.values.id}-${cell.column.id}`}>
diff --git a/front/src/views/Products/components/transformers.ts b/front/src/views/Products/components/transformers.ts
index efafa60b38..5e70f1053b 100644
--- a/front/src/views/Products/components/transformers.ts
+++ b/front/src/views/Products/components/transformers.ts
@@ -37,6 +37,7 @@ export type ProductRow = {
   created?: Date | null;
   createdBy?: string | null;
   id: string;
+  standardInstantiationId?: string;
 };
 
 export const standardProductsRawDataToTableDataTransformer = (
@@ -98,9 +99,11 @@ export const productsRawToTableDataTransformer = (productsRawData: ProductsQuery
         lastModifiedBy,
         createdOn,
         createdBy,
+        standardProduct,
       }) => {
         return {
           id,
+          standardInstantiationId: standardProduct?.id,
           name,
           isStandard: type === "StandardInstantiation",
           category: category.name,


Reviews:
user: login: aerinsol
body: UI approved 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T09:48:06Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T11:54:46Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T12:44:37Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:28:29Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:30:41Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:31:19Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:32:03Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:36:54Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:39:22Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T13:42:07Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:40:11Z
user: login: pylipp
body: ### functional review passes
@fhenrich33 one final (slightly unrelated) remark: in EditCustomProductForm, can you update the subtitle from PRODUCT SETUP to PRODUCT DETAILS?
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:47:33Z
user: login: HaGuesto
body: # code review
generally looks good. @fhenrich33 could you respond to the instantiation question and tell me why you cast the comments to have an empty string?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T14:49:42Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:51:07Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:51:29Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:53:15Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:54:00Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:54:18Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:54:40Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:55:51Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:56:22Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:57:26Z
user: login: pylipp
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:57:47Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T14:58:08Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T15:00:11Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:00:34Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:02:21Z
user: login: HaGuesto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T15:04:38Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:06:25Z
user: login: fhenrich33
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:07:39Z

Review comments:
diff_hunk: @@ -98,9 +99,11 @@ export const productsRawToTableDataTransformer = (productsRawData: ProductsQuery
         lastModifiedBy,
         createdOn,
         createdBy,
+        standardProduct,
       }) => {
         return {
           id,
+          standardInstantiationId: standardProduct?.id,
path: front/src/views/Products/components/transformers.ts
user: login: pylipp
body: I suggest to call this `standardProductId`
EDIT: unless `standardProduct` is actually an instantiation
created_at: 2025-05-21T11:54:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z
+    .string()
+    .optional()
+    .transform((value) => (value === "" ? undefined : value)),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: pylipp
body: @fhenrich33 why did you add the final transformation? I tried to remove the comment of a custom product on staging (which uses the same zod logic) by giving an empty string as input but it's not being included in the GraphQL mutation input.
created_at: 2025-05-21T12:44:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -98,9 +99,11 @@ export const productsRawToTableDataTransformer = (productsRawData: ProductsQuery
         lastModifiedBy,
         createdOn,
         createdBy,
+        standardProduct,
       }) => {
         return {
           id,
+          standardInstantiationId: standardProduct?.id,
path: front/src/views/Products/components/transformers.ts
user: login: fhenrich33
body: Exactly @pylipp, I called it `standardInstantiationId` to better differentiate this.
created_at: 2025-05-21T13:28:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z
+    .string()
+    .optional()
+    .transform((value) => (value === "" ? undefined : value)),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: fhenrich33
body: Interesting, this wasn't caught in the Enable Standard Product PR then. This is using the same Zod Schema.
created_at: 2025-05-21T13:30:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          createdOn
+          createdBy {
+            id
+            name
+          }
+        }
+      }
+    }
+  `,
+  [],
+);
+
+function EditStandardProductFormContainer() {
+  const navigate = useNavigate();
+  const baseId = useAtomValue(selectedBaseIdAtom);
+  const requestedStandardProductId = useParams<{ standardProductId: string }>().standardProductId;
+  const { createToast } = useNotification();
+  const { triggerError } = useErrorHandling();
+  const { data: standardProductsRawData, error } = useSuspenseQuery(STANDARD_PRODUCT_QUERY, {
+    variables: { baseId },
+  });
+
+  const [editStandardProduct, { loading: isEditStandardProductLoading }] = useMutation(
+    EDIT_STANDARD_PRODUCT_MUTATION,
+  );
+
+  const onSubmit = useCallback(
+    (editStandardProductFormOutput: EditStandardProductFormOutput) => {
+      editStandardProduct({
+        variables: {
+          id: editStandardProductFormOutput.instantiation.value,
+          comment: editStandardProductFormOutput.comment,
path: front/src/views/EditStandardProduct/EditStandardProductView.tsx
user: login: pylipp
body: Had to add `|| ""` here to enable submitting an empty comment
created_at: 2025-05-21T13:31:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          createdOn
+          createdBy {
+            id
+            name
+          }
+        }
+      }
+    }
+  `,
+  [],
+);
+
+function EditStandardProductFormContainer() {
+  const navigate = useNavigate();
+  const baseId = useAtomValue(selectedBaseIdAtom);
+  const requestedStandardProductId = useParams<{ standardProductId: string }>().standardProductId;
+  const { createToast } = useNotification();
+  const { triggerError } = useErrorHandling();
+  const { data: standardProductsRawData, error } = useSuspenseQuery(STANDARD_PRODUCT_QUERY, {
+    variables: { baseId },
+  });
+
+  const [editStandardProduct, { loading: isEditStandardProductLoading }] = useMutation(
+    EDIT_STANDARD_PRODUCT_MUTATION,
+  );
+
+  const onSubmit = useCallback(
+    (editStandardProductFormOutput: EditStandardProductFormOutput) => {
+      editStandardProduct({
+        variables: {
+          id: editStandardProductFormOutput.instantiation.value,
+          comment: editStandardProductFormOutput.comment,
path: front/src/views/EditStandardProduct/EditStandardProductView.tsx
user: login: pylipp
body: Same in EditCustomProductView.tsx
created_at: 2025-05-21T13:32:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          createdOn
+          createdBy {
+            id
+            name
+          }
+        }
+      }
+    }
+  `,
+  [],
+);
+
+function EditStandardProductFormContainer() {
+  const navigate = useNavigate();
+  const baseId = useAtomValue(selectedBaseIdAtom);
+  const requestedStandardProductId = useParams<{ standardProductId: string }>().standardProductId;
+  const { createToast } = useNotification();
+  const { triggerError } = useErrorHandling();
+  const { data: standardProductsRawData, error } = useSuspenseQuery(STANDARD_PRODUCT_QUERY, {
+    variables: { baseId },
+  });
+
+  const [editStandardProduct, { loading: isEditStandardProductLoading }] = useMutation(
+    EDIT_STANDARD_PRODUCT_MUTATION,
+  );
+
+  const onSubmit = useCallback(
+    (editStandardProductFormOutput: EditStandardProductFormOutput) => {
+      editStandardProduct({
+        variables: {
+          id: editStandardProductFormOutput.instantiation.value,
+          comment: editStandardProductFormOutput.comment,
path: front/src/views/EditStandardProduct/EditStandardProductView.tsx
user: login: fhenrich33
body: Seems we didn't try to send empty comments on the functional tests for these previous PRs

[[> Interesting, this wasn't caught in the Enable Standard Product PR then. This is using the same Zod Schema.](https://github.com/boxwise/boxtribute/pull/2171#discussion_r2100308277)](https://github.com/boxwise/boxtribute/pull/2171#discussion_r2100308277)
created_at: 2025-05-21T13:36:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          createdOn
+          createdBy {
+            id
+            name
+          }
path: front/src/views/EditStandardProduct/EditStandardProductView.tsx
user: login: pylipp
body: rather than the created fields query `lastModifiedOn`and `lastModifiedBy` because this will automatically updated the ManageProducts table
created_at: 2025-05-21T13:39:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          createdOn
+          createdBy {
+            id
+            name
+          }
path: front/src/views/EditStandardProduct/EditStandardProductView.tsx
user: login: pylipp
body: Also in EditCustomProductView please add the lastModified* fields to the EDIT_CUSTOM_PRODUCT_MUTATION 
created_at: 2025-05-21T13:42:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,7 +110,7 @@ function EditCustomProductFormContainer() {
           gender: editProductFormOutput.gender?.value as NonNullProductGender,
           price: editProductFormOutput.price,
           inShop: editProductFormOutput.inShop,
-          comment: editProductFormOutput.comment,
+          comment: editProductFormOutput.comment || "",
path: front/src/views/EditCustomProduct/EditCustomProductView.tsx
user: login: HaGuesto
body: not sure why you do this, but it actually causes empty strings to be saved in the db which I would not like to do.
created_at: 2025-05-21T14:30:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+import { useEffect, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+  HStack,
+  Stack,
+  Button,
+  Box,
+  Text,
+  Skeleton,
+  Select,
+  FormControl,
+  FormLabel,
+  VStack,
+  Input,
+} from "@chakra-ui/react";
+import { useAtomValue } from "jotai";
+
+import { AlertWithoutAction } from "components/Alerts";
+import { NewNumberField } from "components/Form/NumberField";
+import SelectField from "components/Form/SelectField";
+import SwitchField from "components/Form/SwitchField";
+import { selectedBaseAtom } from "stores/globalPreferenceStore";
+import { StandardProductFormSchema } from "views/EnableStandardProduct/components/transformer";
+
+export type EditStandardProductFormInput = z.input<typeof StandardProductFormSchema>;
+export type EditStandardProductFormOutput = z.output<typeof StandardProductFormSchema>;
+
+export type EditStandardProductFormProps = {
+  showAlert: boolean;
+  isLoading: boolean;
+  standardProductData: EditStandardProductFormInput[];
+  defaultValues?: EditStandardProductFormInput;
+  onSubmit: (editStandardProductFormData: EditStandardProductFormOutput) => void;
+};
+
+function EditStandardProductForm({
+  showAlert,
+  isLoading,
+  standardProductData,
+  defaultValues,
+  onSubmit,
+}: EditStandardProductFormProps) {
+  const navigate = useNavigate();
+  const selectedBase = useAtomValue(selectedBaseAtom);
+  const baseName = selectedBase?.name;
+
+  const {
+    handleSubmit,
+    control,
+    register,
+    watch,
+    formState: { errors },
+  } = useForm<EditStandardProductFormInput>({
+    resolver: zodResolver(StandardProductFormSchema),
+    ...(defaultValues ? { defaultValues } : {}),
+  });
+
+  const selectedStandardProduct = watch("standardProduct");
+
+  useEffect(() => {
+    if (defaultValues && selectedStandardProduct.value !== defaultValues.standardProduct.value) {
+      navigate(`../${selectedStandardProduct.value}`);
+    }
+    if (!defaultValues && !!selectedStandardProduct) {
+      navigate(`${selectedStandardProduct.value}`);
+    }
+  }, [defaultValues, navigate, selectedStandardProduct]);
+
+  const sortedProductOptions = useMemo(() => {
+    return [...standardProductData]
+      .sort((a, b) => a.standardProduct.label.localeCompare(b.standardProduct.label))
+      .map((data) => ({
+        label: data.standardProduct.label,
+        value: data.standardProduct.value,
+        data: { gender: data.gender },
+      }));
+  }, [standardProductData]);
+
+  return (
+    <>
+      {showAlert && (
+        <AlertWithoutAction
+          type="info"
+          closeable={true}
+          alertText=" For ASSORT standard products, only the product description, and free shop settings can be edited."
+          mb={2}
+        />
+      )}
+      <form onSubmit={handleSubmit(onSubmit)}>
+        <Box border="2px" mb={4} p={2}>
+          <HStack borderBottom="2px" pb={2} px={2}>
+            <Text fontWeight="bold" fontSize="md">
+              {baseName ? baseName?.toUpperCase() : <Skeleton height={6} width={20} />}
+            </Text>
+            <Text fontWeight="bold" fontSize="md">
+              PRODUCT DETAILS
+            </Text>
+          </HStack>
+          <VStack>
+            <VStack p={2} w="full" bg="gray.100" borderRadius={10} mt={2}>
+              <SelectField
path: front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx
user: login: pylipp
body: This must not be selectable. Otherwise the user can select a standard product that they don't have enabled which results in sending the EditStandardProductMutation to the backend with empty `id` input.
As a result I think it's not required to pass standardProductData into the component, and running the STANDARD_PRODUCT_QUERY can be avoided entirely in EditStandardProductView
created_at: 2025-05-21T14:40:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: HaGuesto
body: you do not need this in both forms  - EditStandardProductFomr and EnableStandardProductForm. So, why was this added?

created_at: 2025-05-21T14:48:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,227 @@
+import { useEffect, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+  HStack,
+  Stack,
+  Button,
+  Box,
+  Text,
+  Skeleton,
+  Select,
+  FormControl,
+  FormLabel,
+  VStack,
+  Input,
+} from "@chakra-ui/react";
+import { useAtomValue } from "jotai";
+
+import { AlertWithoutAction } from "components/Alerts";
+import { NewNumberField } from "components/Form/NumberField";
+import SelectField from "components/Form/SelectField";
+import SwitchField from "components/Form/SwitchField";
+import { selectedBaseAtom } from "stores/globalPreferenceStore";
+import { StandardProductFormSchema } from "views/EnableStandardProduct/components/transformer";
+
+export type EditStandardProductFormInput = z.input<typeof StandardProductFormSchema>;
+export type EditStandardProductFormOutput = z.output<typeof StandardProductFormSchema>;
+
+export type EditStandardProductFormProps = {
+  showAlert: boolean;
+  isLoading: boolean;
+  standardProductData: EditStandardProductFormInput[];
+  defaultValues?: EditStandardProductFormInput;
+  onSubmit: (editStandardProductFormData: EditStandardProductFormOutput) => void;
+};
+
+function EditStandardProductForm({
+  showAlert,
+  isLoading,
+  standardProductData,
+  defaultValues,
+  onSubmit,
+}: EditStandardProductFormProps) {
+  const navigate = useNavigate();
+  const selectedBase = useAtomValue(selectedBaseAtom);
+  const baseName = selectedBase?.name;
+
+  const {
+    handleSubmit,
+    control,
+    register,
+    watch,
+    formState: { errors },
+  } = useForm<EditStandardProductFormInput>({
+    resolver: zodResolver(StandardProductFormSchema),
+    ...(defaultValues ? { defaultValues } : {}),
+  });
+
+  const selectedStandardProduct = watch("standardProduct");
+
+  useEffect(() => {
+    if (defaultValues && selectedStandardProduct.value !== defaultValues.standardProduct.value) {
+      navigate(`../${selectedStandardProduct.value}`);
+    }
+    if (!defaultValues && !!selectedStandardProduct) {
+      navigate(`${selectedStandardProduct.value}`);
+    }
+  }, [defaultValues, navigate, selectedStandardProduct]);
+
+  const sortedProductOptions = useMemo(() => {
+    return [...standardProductData]
+      .sort((a, b) => a.standardProduct.label.localeCompare(b.standardProduct.label))
+      .map((data) => ({
+        label: data.standardProduct.label,
+        value: data.standardProduct.value,
+        data: { gender: data.gender },
+      }));
+  }, [standardProductData]);
+
+  return (
+    <>
+      {showAlert && (
+        <AlertWithoutAction
+          type="info"
+          closeable={true}
+          alertText=" For ASSORT standard products, only the product description, and free shop settings can be edited."
+          mb={2}
+        />
+      )}
+      <form onSubmit={handleSubmit(onSubmit)}>
+        <Box border="2px" mb={4} p={2}>
+          <HStack borderBottom="2px" pb={2} px={2}>
+            <Text fontWeight="bold" fontSize="md">
+              {baseName ? baseName?.toUpperCase() : <Skeleton height={6} width={20} />}
+            </Text>
+            <Text fontWeight="bold" fontSize="md">
+              PRODUCT DETAILS
+            </Text>
+          </HStack>
+          <VStack>
+            <VStack p={2} w="full" bg="gray.100" borderRadius={10} mt={2}>
+              <SelectField
path: front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx
user: login: HaGuesto
body: correct
created_at: 2025-05-21T14:51:07Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: fhenrich33
body: Unified this since it doesn't affect the EnableStandardProductForm, and we can get away with it.
created_at: 2025-05-21T14:51:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,7 +110,7 @@ function EditCustomProductFormContainer() {
           gender: editProductFormOutput.gender?.value as NonNullProductGender,
           price: editProductFormOutput.price,
           inShop: editProductFormOutput.inShop,
-          comment: editProductFormOutput.comment,
+          comment: editProductFormOutput.comment || "",
path: front/src/views/EditCustomProduct/EditCustomProductView.tsx
user: login: pylipp
body: @HaGuesto please check the conversation that Felipe and I had. 
This is necessary in order to reset a comment
created_at: 2025-05-21T14:53:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z
+    .string()
+    .optional()
+    .transform((value) => (value === "" ? undefined : value)),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: HaGuesto
body: ah interesting. I added this. So, @pylipp what would the input to the muatation need to be to make the comment null in the backend? Should we transform it to a null instead of a undefined or just remove it?
created_at: 2025-05-21T14:54:00Z
author_association: MEMBER
diff_hunk: @@ -105,7 +110,7 @@ function EditCustomProductFormContainer() {
           gender: editProductFormOutput.gender?.value as NonNullProductGender,
           price: editProductFormOutput.price,
           inShop: editProductFormOutput.inShop,
-          comment: editProductFormOutput.comment,
+          comment: editProductFormOutput.comment || "",
path: front/src/views/EditCustomProduct/EditCustomProductView.tsx
user: login: pylipp
body: Also, saving an empty string as product or box comment is the default in dropapp
created_at: 2025-05-21T14:54:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,227 @@
+import { useEffect, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+  HStack,
+  Stack,
+  Button,
+  Box,
+  Text,
+  Skeleton,
+  Select,
+  FormControl,
+  FormLabel,
+  VStack,
+  Input,
+} from "@chakra-ui/react";
+import { useAtomValue } from "jotai";
+
+import { AlertWithoutAction } from "components/Alerts";
+import { NewNumberField } from "components/Form/NumberField";
+import SelectField from "components/Form/SelectField";
+import SwitchField from "components/Form/SwitchField";
+import { selectedBaseAtom } from "stores/globalPreferenceStore";
+import { StandardProductFormSchema } from "views/EnableStandardProduct/components/transformer";
+
+export type EditStandardProductFormInput = z.input<typeof StandardProductFormSchema>;
+export type EditStandardProductFormOutput = z.output<typeof StandardProductFormSchema>;
+
+export type EditStandardProductFormProps = {
+  showAlert: boolean;
+  isLoading: boolean;
+  standardProductData: EditStandardProductFormInput[];
+  defaultValues?: EditStandardProductFormInput;
+  onSubmit: (editStandardProductFormData: EditStandardProductFormOutput) => void;
+};
+
+function EditStandardProductForm({
+  showAlert,
+  isLoading,
+  standardProductData,
+  defaultValues,
+  onSubmit,
+}: EditStandardProductFormProps) {
+  const navigate = useNavigate();
+  const selectedBase = useAtomValue(selectedBaseAtom);
+  const baseName = selectedBase?.name;
+
+  const {
+    handleSubmit,
+    control,
+    register,
+    watch,
+    formState: { errors },
+  } = useForm<EditStandardProductFormInput>({
+    resolver: zodResolver(StandardProductFormSchema),
+    ...(defaultValues ? { defaultValues } : {}),
+  });
+
+  const selectedStandardProduct = watch("standardProduct");
+
+  useEffect(() => {
+    if (defaultValues && selectedStandardProduct.value !== defaultValues.standardProduct.value) {
+      navigate(`../${selectedStandardProduct.value}`);
+    }
+    if (!defaultValues && !!selectedStandardProduct) {
+      navigate(`${selectedStandardProduct.value}`);
+    }
+  }, [defaultValues, navigate, selectedStandardProduct]);
+
+  const sortedProductOptions = useMemo(() => {
+    return [...standardProductData]
+      .sort((a, b) => a.standardProduct.label.localeCompare(b.standardProduct.label))
+      .map((data) => ({
+        label: data.standardProduct.label,
+        value: data.standardProduct.value,
+        data: { gender: data.gender },
+      }));
+  }, [standardProductData]);
+
+  return (
+    <>
+      {showAlert && (
+        <AlertWithoutAction
+          type="info"
+          closeable={true}
+          alertText=" For ASSORT standard products, only the product description, and free shop settings can be edited."
+          mb={2}
+        />
+      )}
+      <form onSubmit={handleSubmit(onSubmit)}>
+        <Box border="2px" mb={4} p={2}>
+          <HStack borderBottom="2px" pb={2} px={2}>
+            <Text fontWeight="bold" fontSize="md">
+              {baseName ? baseName?.toUpperCase() : <Skeleton height={6} width={20} />}
+            </Text>
+            <Text fontWeight="bold" fontSize="md">
+              PRODUCT DETAILS
+            </Text>
+          </HStack>
+          <VStack>
+            <VStack p={2} w="full" bg="gray.100" borderRadius={10} mt={2}>
+              <SelectField
path: front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx
user: login: HaGuesto
body: @fhenrich33 just copy the behavior of category, gender or sizegroup
created_at: 2025-05-21T14:54:40Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: HaGuesto
body: but you also do not have it in the EditStandardProduct as a form field.
created_at: 2025-05-21T14:55:51Z
author_association: MEMBER
diff_hunk: @@ -105,7 +110,7 @@ function EditCustomProductFormContainer() {
           gender: editProductFormOutput.gender?.value as NonNullProductGender,
           price: editProductFormOutput.price,
           inShop: editProductFormOutput.inShop,
-          comment: editProductFormOutput.comment,
+          comment: editProductFormOutput.comment || "",
path: front/src/views/EditCustomProduct/EditCustomProductView.tsx
user: login: fhenrich33
body: Not coercing comment values anymore.
created_at: 2025-05-21T14:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,7 +110,7 @@ function EditCustomProductFormContainer() {
           gender: editProductFormOutput.gender?.value as NonNullProductGender,
           price: editProductFormOutput.price,
           inShop: editProductFormOutput.inShop,
-          comment: editProductFormOutput.comment,
+          comment: editProductFormOutput.comment || "",
path: front/src/views/EditCustomProduct/EditCustomProductView.tsx
user: login: fhenrich33
body: @pylipp The value sent by the form will be `""` if field is empty.
created_at: 2025-05-21T14:57:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z
+    .string()
+    .optional()
+    .transform((value) => (value === "" ? undefined : value)),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: pylipp
body: the update-product logic in the BE only changes the comment if the input value is different from null. Hence transforming it to null won't have any effect. It needs an empty string which we now achieve through the ` || ""` elsewhere
created_at: 2025-05-21T14:57:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -105,7 +110,7 @@ function EditCustomProductFormContainer() {
           gender: editProductFormOutput.gender?.value as NonNullProductGender,
           price: editProductFormOutput.price,
           inShop: editProductFormOutput.inShop,
-          comment: editProductFormOutput.comment,
+          comment: editProductFormOutput.comment || "",
path: front/src/views/EditCustomProduct/EditCustomProductView.tsx
user: login: HaGuesto
body: ok, but rather then adding a second transformation here, I'd adjust the zod transformation.
created_at: 2025-05-21T14:58:08Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z
+    .string()
+    .optional()
+    .transform((value) => (value === "" ? undefined : value)),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: HaGuesto
body: @fhenrich33 pls remove this transform function here instead of casting the comment to be always an empty string before the submit
created_at: 2025-05-21T15:00:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,177 @@
+import { Suspense, useCallback } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useMutation, useSuspenseQuery } from "@apollo/client";
+import { useAtomValue } from "jotai";
+import { ErrorBoundary } from "@sentry/react";
+import { Box, Center, Heading } from "@chakra-ui/react";
+
+import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation";
+import { graphql } from "../../../../graphql/graphql";
+import { AlertWithoutAction } from "components/Alerts";
+import { FormSkeleton } from "components/Skeletons";
+import { selectedBaseIdAtom } from "stores/globalPreferenceStore";
+import { useNotification } from "hooks/useNotification";
+import { useErrorHandling } from "hooks/useErrorHandling";
+import {
+  findDefaultValues,
+  standardProductRawToFormDataTransformer,
+} from "views/EnableStandardProduct/components/transformer";
+import EditStandardProductForm, {
+  EditStandardProductFormOutput,
+} from "./components/EditStandardProductForm";
+import { STANDARD_PRODUCT_QUERY } from "queries/queries";
+
+export const StandardProductQueryErrorText =
+  "Could not fetch ASSORT standard data! Please try reloading the page.";
+
+export const EDIT_STANDARD_PRODUCT_MUTATION = graphql(
+  `
+    mutation EditStandardProductMutation(
+      $id: ID!
+      $comment: String
+      $inShop: Boolean
+      $price: Int
+    ) {
+      editStandardProductInstantiation(
+        editInput: { id: $id, comment: $comment, inShop: $inShop, price: $price }
+      ) {
+        __typename
+        ... on Product {
+          id
+          type
+          standardProduct {
+            id
+          }
+          comment
+          inShop
+          price
+          lastModifiedOn
+          lastModifiedBy {
+            id
+            name
+          }
+        }
+      }
+    }
+  `,
+  [],
+);
+
+function EditStandardProductFormContainer() {
+  const navigate = useNavigate();
+  const baseId = useAtomValue(selectedBaseIdAtom);
+  const requestedStandardProductId = useParams<{ standardProductId: string }>().standardProductId;
+  const { createToast } = useNotification();
+  const { triggerError } = useErrorHandling();
+  const { data: standardProductsRawData, error } = useSuspenseQuery(STANDARD_PRODUCT_QUERY, {
+    variables: { baseId },
+  });
+
+  const [editStandardProduct, { loading: isEditStandardProductLoading }] = useMutation(
+    EDIT_STANDARD_PRODUCT_MUTATION,
+  );
+
+  const onSubmit = useCallback(
+    (editStandardProductFormOutput: EditStandardProductFormOutput) => {
+      editStandardProduct({
+        variables: {
+          id: editStandardProductFormOutput.instantiation.value,
+          comment: editStandardProductFormOutput.comment || "",
path: front/src/views/EditStandardProduct/EditStandardProductView.tsx
user: login: fhenrich33
body: It's already committed without this coercion or transformation, comment will be `""` if field is empty.
created_at: 2025-05-21T15:00:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: fhenrich33
body: I only need to pass the value to the mutation; if I'm to do it some other way, it's too much of a roundabout.
created_at: 2025-05-21T15:02:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: HaGuesto
body: ok
created_at: 2025-05-21T15:04:38Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,50 @@
-import {
-  enableStandardProductQueryErrorText,
-  IEnableStandardProductQueryResultType,
-} from "../EnableStandardProductView";
-import { IEnableStandardProductFormInput } from "./EnableStandardProductForm";
+import { z } from "zod";
+
+import { StandardProductQueryResultType } from "queries/types";
+import { enableStandardProductQueryErrorText } from "../EnableStandardProductView";
+import { EnableStandardProductFormInput } from "./EnableStandardProductForm";
+
+const SingleSelectOptionSchema = z.object({
+  label: z.string(),
+  value: z.string(),
+});
+
+export const StandardProductFormSchema = z.object({
+  // see BoxEdit.tsx how to validate a select field
+  instantiation: z.object({ value: z.string(), label: z.string() }),
+  standardProduct: z.object(
+    { value: z.string(), label: z.string() },
+    { required_error: "Please select a standard product." },
+  ),
+  category: SingleSelectOptionSchema.optional(),
+  gender: z.string().optional(),
+  sizeRange: SingleSelectOptionSchema.optional(),
+  comment: z
+    .string()
+    .optional()
+    .transform((value) => (value === "" ? undefined : value)),
path: front/src/views/EnableStandardProduct/components/transformer.ts
user: login: fhenrich33
body: https://github.com/boxwise/boxtribute/pull/2171/commits/55c8985239765d553d728008153cdc81633b0b17
created_at: 2025-05-21T15:06:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,227 @@
+import { useEffect, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import { zodResolver } from "@hookform/resolvers/zod";
+import {
+  HStack,
+  Stack,
+  Button,
+  Box,
+  Text,
+  Skeleton,
+  Select,
+  FormControl,
+  FormLabel,
+  VStack,
+  Input,
+} from "@chakra-ui/react";
+import { useAtomValue } from "jotai";
+
+import { AlertWithoutAction } from "components/Alerts";
+import { NewNumberField } from "components/Form/NumberField";
+import SelectField from "components/Form/SelectField";
+import SwitchField from "components/Form/SwitchField";
+import { selectedBaseAtom } from "stores/globalPreferenceStore";
+import { StandardProductFormSchema } from "views/EnableStandardProduct/components/transformer";
+
+export type EditStandardProductFormInput = z.input<typeof StandardProductFormSchema>;
+export type EditStandardProductFormOutput = z.output<typeof StandardProductFormSchema>;
+
+export type EditStandardProductFormProps = {
+  showAlert: boolean;
+  isLoading: boolean;
+  standardProductData: EditStandardProductFormInput[];
+  defaultValues?: EditStandardProductFormInput;
+  onSubmit: (editStandardProductFormData: EditStandardProductFormOutput) => void;
+};
+
+function EditStandardProductForm({
+  showAlert,
+  isLoading,
+  standardProductData,
+  defaultValues,
+  onSubmit,
+}: EditStandardProductFormProps) {
+  const navigate = useNavigate();
+  const selectedBase = useAtomValue(selectedBaseAtom);
+  const baseName = selectedBase?.name;
+
+  const {
+    handleSubmit,
+    control,
+    register,
+    watch,
+    formState: { errors },
+  } = useForm<EditStandardProductFormInput>({
+    resolver: zodResolver(StandardProductFormSchema),
+    ...(defaultValues ? { defaultValues } : {}),
+  });
+
+  const selectedStandardProduct = watch("standardProduct");
+
+  useEffect(() => {
+    if (defaultValues && selectedStandardProduct.value !== defaultValues.standardProduct.value) {
+      navigate(`../${selectedStandardProduct.value}`);
+    }
+    if (!defaultValues && !!selectedStandardProduct) {
+      navigate(`${selectedStandardProduct.value}`);
+    }
+  }, [defaultValues, navigate, selectedStandardProduct]);
+
+  const sortedProductOptions = useMemo(() => {
+    return [...standardProductData]
+      .sort((a, b) => a.standardProduct.label.localeCompare(b.standardProduct.label))
+      .map((data) => ({
+        label: data.standardProduct.label,
+        value: data.standardProduct.value,
+        data: { gender: data.gender },
+      }));
+  }, [standardProductData]);
+
+  return (
+    <>
+      {showAlert && (
+        <AlertWithoutAction
+          type="info"
+          closeable={true}
+          alertText=" For ASSORT standard products, only the product description, and free shop settings can be edited."
+          mb={2}
+        />
+      )}
+      <form onSubmit={handleSubmit(onSubmit)}>
+        <Box border="2px" mb={4} p={2}>
+          <HStack borderBottom="2px" pb={2} px={2}>
+            <Text fontWeight="bold" fontSize="md">
+              {baseName ? baseName?.toUpperCase() : <Skeleton height={6} width={20} />}
+            </Text>
+            <Text fontWeight="bold" fontSize="md">
+              PRODUCT DETAILS
+            </Text>
+          </HStack>
+          <VStack>
+            <VStack p={2} w="full" bg="gray.100" borderRadius={10} mt={2}>
+              <SelectField
path: front/src/views/EditStandardProduct/components/EditStandardProductForm.tsx
user: login: fhenrich33
body: Already resolved @hans https://github.com/boxwise/boxtribute/pull/2171/commits/045c45f66c6022124550e3ea6411254e8509835c
created_at: 2025-05-21T15:07:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-21T09:21:59Z
body: ## [Codecov](https://app.codecov.io/gh/boxwise/boxtribute/pull/2171?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 76.00%. Comparing base [(`48c5b7f`)](https://app.codecov.io/gh/boxwise/boxtribute/commit/48c5b7f936a4f29ff6babee497f5d94fab469860?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise) to head [(`55c8985`)](https://app.codecov.io/gh/boxwise/boxtribute/commit/55c8985239765d553d728008153cdc81633b0b17?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise).
> Report is 6 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #2171   +/-   ##
=======================================
  Coverage   76.00%   76.00%           
=======================================
  Files         271      271           
  Lines       18501    18501           
  Branches     1747     1747           
=======================================
  Hits        14062    14062           
  Misses       4399     4399           
  Partials       40       40           
```

| [Flag](https://app.codecov.io/gh/boxwise/boxtribute/pull/2171/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise) | Coverage Δ | |
|---|---|---|
| [backend](https://app.codecov.io/gh/boxwise/boxtribute/pull/2171/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise) | `99.66% <ø> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/boxwise/boxtribute/pull/2171?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boxwise).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: pylipp
created_at: 2025-05-21T12:06:58Z
body: https://trello.com/c/jCRxX8JN/1746-20-edit-price-comment-and-stockroom-setting-for-standardproduct-instantiation-small
author_association: CONTRIBUTOR
user: login: HaGuesto
created_at: 2025-05-21T15:07:07Z
body: @pylipp good enough from my side. ready to merge.
author_association: MEMBER