FarmBot/Farmbot-Web-App

View on GitHub
frontend/plants/curve_info.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import React from "react";
import { countBy, sortBy, uniq } from "lodash";
import { t } from "../i18next_wrapper";
import { Path } from "../internal_urls";
import { Link } from "../link";
import { AllCurveInfoProps, CurveInfoProps } from "../curves/interfaces";
import { CurveSvgWithPopover } from "../curves/chart";
import { betterCompact } from "../util";
import { TaggedCurve, TaggedPlantPointer } from "farmbot";
import { CurveType } from "../curves/templates";
import { PlantPointer } from "farmbot/dist/resources/api_resources";
import { curveInfo, CURVE_TYPES } from "../curves/curves_inventory";
import { DropDownItem, FBSelect, NULL_CHOICE } from "../ui";
import { Actions } from "../constants";
import { FormattedPlantInfo } from "./map_state_to_props";
import { DesignerState } from "../farm_designer/interfaces";

export const AllCurveInfo = (props: AllCurveInfoProps) => {
  return <div className={"all-curve-info"}>
    {[CurveType.water, CurveType.spread, CurveType.height].map(curveType => {
      return <CurveInfo key={curveType}
        dispatch={props.dispatch}
        curveType={curveType}
        plant={props.plant}
        curve={props.findCurve(curveType)}
        curves={props.curves}
        plants={props.plants}
        onChange={props.onChange}
        farmwareEnvs={props.farmwareEnvs}
        soilHeightPoints={props.soilHeightPoints}
        sourceFbosConfig={props.sourceFbosConfig}
        botSize={props.botSize} />;
    })}
  </div>;
};

export const CurveInfo = (props: CurveInfoProps) => {
  const { plant, onChange, curve, curves, plants, curveType } = props;
  const [hovered, setHovered] = React.useState<string | undefined>(undefined);
  return <div className={"crop-curve-info"}>
    <div className={"active-curve-name"}>
      <label>{t(CURVE_TYPES()[props.curveType])}</label>
      <FBSelect key={curve?.uuid}
        list={curvesDropdownList({ curves, curveType, plants, plant })}
        selectedItem={curve ? curveToDdi(curve) : undefined}
        onChange={ddi => {
          (ddi.headingId || ddi.isNull)
            && onChange(ddi.isNull ? undefined : ddi.value, props.curveType);
        }} />
      {curve && <Link to={Path.curves(curve.body.id)} title={t("edit curve")}>
        <i className={"fa fa-external-link fb-icon-button"} />
      </Link>}
    </div>
    {curve && <CurveSvgWithPopover dispatch={props.dispatch} curve={curve}
      x={plant?.x} y={plant?.y}
      farmwareEnvs={props.farmwareEnvs}
      soilHeightPoints={props.soilHeightPoints}
      sourceFbosConfig={props.sourceFbosConfig}
      hovered={hovered || "" + plant?.daysOld} setHovered={setHovered}
      botSize={props.botSize} editable={false} />}
  </div>;
};

interface CurvesDropdownListProps {
  curves: TaggedCurve[];
  curveType: CurveType;
  plants: TaggedPlantPointer[];
  plant?: FormattedPlantInfo;
}

const curvesDropdownList = (props: CurvesDropdownListProps) => {
  const openfarmSlug = props.plant?.slug || Path.getSlug(Path.cropSearch());
  const list: (DropDownItem | undefined)[] = [];
  list.push(NULL_CHOICE);
  const usedIds = props.plants
    .filter(plant => plant.body.openfarm_slug == openfarmSlug)
    .map(plant => plant.body[CURVE_KEY_LOOKUP[props.curveType]])
    .filter(id => id);
  list.push({
    label: t("Currently used with this crop"), value: "", heading: true,
  });
  sortBy(Object.entries(countBy(usedIds)), ([_id, count]) => count)
    .reverse()
    .map(([id, count]) => {
      const curve = props.curves.filter(c => c.body.id == parseInt(id))[0];
      list.push(curveToDdi(curve, count));
    });
  list.push({
    label: t("Other curves"), value: "", heading: true,
  });
  props.curves.filter(curve => curve.body.type == props.curveType)
    .filter(curve => !uniq(usedIds).includes(curve.body.id))
    .map(curve => list.push(curveToDdi(curve)));
  return betterCompact(list);
};

export const curveToDdi =
  (curve: TaggedCurve, useCount?: number): DropDownItem | undefined => {
    const uses = useCount ? ` (${useCount})` : "";
    return curve.body.id
      ? ({
        label: `${curve.body.name} - ${curveInfo(curve)}${uses}`,
        value: curve.body.id,
        headingId: curve.body.type,
      })
      : undefined;
  };

interface FindCurveProps {
  openfarmSlug: string;
  plants: TaggedPlantPointer[];
  curves: TaggedCurve[];
}

export const findMostUsedCurveForCrop = (props: FindCurveProps) =>
  (curveType: CurveType): TaggedCurve | undefined => {
    const { openfarmSlug, plants, curves } = props;
    const counts = countBy(plants
      .filter(p => p.body.openfarm_slug == openfarmSlug)
      .map(p => p.body[CURVE_KEY_LOOKUP[curveType]] as number | undefined)
      .filter(x => x));
    const maxCount = Math.max(...Object.values(counts));
    const curveId = Object.entries(counts)
      .filter(([id, _count]) => id != "undefined")
      .filter(([_id, count]) => count == maxCount)[0]?.[0];
    return curves.filter(curve => curve.body.id == parseInt(curveId))[0];
  };

export const CURVE_KEY_LOOKUP: Record<CurveType, keyof PlantPointer> = {
  [CurveType.water]: "water_curve_id",
  [CurveType.spread]: "spread_curve_id",
  [CurveType.height]: "height_curve_id",
};

export const CURVE_ACTION_LOOKUP: Record<CurveType, Actions> = {
  [CurveType.water]: Actions.SET_CROP_WATER_CURVE_ID,
  [CurveType.spread]: Actions.SET_CROP_SPREAD_CURVE_ID,
  [CurveType.height]: Actions.SET_CROP_HEIGHT_CURVE_ID,
};

export const curveId = (designer: DesignerState) => {
  return {
    [CurveType.water]: designer.cropWaterCurveId,
    [CurveType.spread]: designer.cropSpreadCurveId,
    [CurveType.height]: designer.cropHeightCurveId,
  };
};

export const findCurve = (curves: TaggedCurve[], designer: DesignerState) =>
  (curveType: CurveType): TaggedCurve | undefined =>
    curves.filter(curve => curve.body.id &&
      curve.body.id == curveId(designer)[curveType])[0];

export const changeCurve = (dispatch: Function) =>
  (id: string | number | undefined, curveType: CurveType) => {
    dispatch({ type: CURVE_ACTION_LOOKUP[curveType], payload: id });
  };