baublet/w8mngr

View on GitHub
client/components/Foods/FoodForm.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import cx from "classnames";
import React from "react";

import { or } from "../../../shared";
import { useForm } from "../../helpers";
import { SecondaryButton } from "../Button/Secondary";
import { ContentContainer } from "../Containers/ContentContainer";
import { ContentLayout } from "../Containers/ContentLayout";
import { Input, MultilineInput } from "../Forms";
import { Upload } from "../Forms/Upload";
import { Spacer } from "../Spacer";
import {
  FoodMeasurementsForm,
  FormMeasurementInput,
} from "./FoodMeasurementsForm";

type MeasurementInput = Omit<FormMeasurementInput, "internalId">;

type FormData = {
  name: string;
  description: string;
  imageUploadId: string;
  selectedUploadIds: string[];
  measurements: MeasurementInput[];
};
type PartialFormData = { [K in keyof FormData]?: FormData[K] | null };

export function FoodForm({
  initialValues,
  onSave,
  loading,
}: {
  initialValues?: PartialFormData;
  onSave: (formState: PartialFormData) => void;
  loading: boolean;
}) {
  const foodFormData = useForm<FormData>({
    initialValues,
  });

  React.useEffect(() => {
    if (initialValues) {
      foodFormData.setValues(initialValues);
    }
  }, [initialValues]);

  const handleSave = React.useCallback(
    (event?: React.FormEvent<HTMLFormElement>) => {
      if (event) {
        event.preventDefault();
      }
      onSave(foodFormData.getValues());
    },
    []
  );

  React.useEffect(() => {
    const ids = foodFormData.getValue("selectedUploadIds");
    foodFormData.setValue(
      "imageUploadId",
      Array.isArray(ids) ? ids[0] || null : undefined || null
    );
  }, [foodFormData.getValue("selectedUploadIds")]);

  const defaultSelectedUploadId = initialValues?.imageUploadId;
  const defaultSelectedUploadIds = defaultSelectedUploadId
    ? [defaultSelectedUploadId]
    : [];

  const initialMeasurements = React.useMemo(() => {
    const measurements = initialValues?.measurements;
    if (measurements) {
      return measurements.map((measurement) => ({
        ...measurement,
        internalId: or(measurement.id, ""),
      }));
    }
    return [];
  }, [initialValues]);

  return (
    <ContentContainer
      className={cx({ "opacity-50 pointer-events-none blur": loading })}
    >
      <ContentLayout
        mainContent={
          <form onSubmit={handleSave}>
            <div className="flex w-full flex-col">
              <Input
                placeholder="Food name"
                type="text"
                label="Name"
                showLabel={false}
                onChange={foodFormData.getHandler("name")}
                value={foodFormData.getValue("name")}
              />
              <Spacer size="s" />
              <MultilineInput
                placeholder="Description"
                type="text"
                label="Description"
                onChange={foodFormData.getHandler("description")}
                value={foodFormData.getValue("description")}
              />
              <Spacer />
              <FoodMeasurementsForm
                initialData={initialMeasurements}
                onChange={foodFormData.getHandler("measurements")}
              />
              <Spacer />
              <div className="flex w-full gap-4 justify-end">
                <SecondaryButton
                  onClick={handleSave}
                  type="submit"
                  disabled={loading}
                >
                  Save Food
                </SecondaryButton>
              </div>
            </div>
          </form>
        }
        sideContent={
          <>
            <Upload
              onChange={foodFormData.getHandler("selectedUploadIds")}
              defaultSelectedUploadIds={defaultSelectedUploadIds}
              placeholder={
                <span>
                  <b>Food image</b> (optional)
                </span>
              }
            />
          </>
        }
      />
    </ContentContainer>
  );
}