Farmbot-Web-App/frontend/farm_designer/point_groups/criteria/component.tsx

206 lines
7.9 KiB
TypeScript

import * as React from "react";
import { t } from "../../../i18next_wrapper";
import {
DaySelection, EqCriteriaSelection, SubCriteriaSection,
NumberCriteriaSelection, LocationSelection, togglePointTypeCriteria,
} from ".";
import {
GroupCriteriaProps, GroupPointCountBreakdownProps, GroupCriteriaState,
DEFAULT_CRITERIA, ClearCriteriaProps, ClearPointIdsProps, POINTER_TYPES,
PointerType,
PointTypeSelectionProps,
} from "./interfaces";
import { ToggleButton } from "../../../controls/toggle_button";
import { Popover } from "@blueprintjs/core";
import { selectPoint } from "../../map/actions";
import { FBSelect, Checkbox, Help } from "../../../ui";
import {
POINTER_TYPE_LIST, POINTER_TYPE_DDI_LOOKUP, isPointType, validPointTypes,
setSelectionPointType,
} from "../../plants/select_plants";
import { ToolTips } from "../../../constants";
import { overwriteGroup } from "../actions";
import { sortGroupBy } from "../point_group_sort";
import { PointGroupItem } from "../point_group_item";
import { Feature } from "../../../devices/interfaces";
import { TaggedPoint } from "farmbot";
export const CRITERIA_POINT_TYPE_LOOKUP =
(): Record<PointerType, string> => ({
Plant: t("Plants"),
GenericPointer: t("Points"),
Weed: t("Weeds"),
ToolSlot: t("Slots"),
});
export class GroupCriteria extends
React.Component<GroupCriteriaProps, GroupCriteriaState> {
state: GroupCriteriaState = {
advanced: false, clearCount: 0, dayChanged: false
};
componentDidMount() {
const { pointer_type } = this.props.group.body.criteria.string_eq;
this.props.dispatch(setSelectionPointType(validPointTypes(pointer_type)));
}
AdvancedToggleMenu = () =>
<div className="criteria-options-menu">
<label>{t("advanced mode")}</label>
<ToggleButton
title={t("toggle advanced view")}
toggleValue={this.state.advanced}
customText={{ textTrue: t("on"), textFalse: t("off") }}
toggleAction={() =>
this.setState({ advanced: !this.state.advanced })} />
</div>
changeDay = (state: boolean) => this.setState({ dayChanged: state });
render() {
const { group, dispatch, slugs } = this.props;
const { criteria } = group.body;
const commonProps = { group, criteria, dispatch };
const dayProps = {
dayChanged: this.state.dayChanged,
changeDay: this.changeDay,
advanced: this.state.advanced,
};
const pointTypes = validPointTypes(criteria.string_eq.pointer_type) || [];
return <div className="group-criteria">
<label className="criteria-heading">{t("filters")}</label>
<Help text={t(ToolTips.CRITERIA_ALPHA_FEATURE)}
customIcon={"exclamation-triangle"} customClass={"alpha-icon"} />
<Popover>
<i className="fa fa-gear dark" />
<this.AdvancedToggleMenu />
</Popover>
{!this.state.advanced
? <div className={"basic"}>
<PointTypeSelection {...commonProps} pointTypes={pointTypes} />
<div className={"point-type-checkboxes"}>
<SubCriteriaSection pointerTypes={pointTypes}
disabled={false} group={group} dispatch={dispatch} slugs={slugs} />
</div>
{!pointTypes.includes("ToolSlot") &&
<DaySelection {...commonProps} {...dayProps} />}
<LocationSelection {...commonProps} botSize={this.props.botSize}
editGroupAreaInMap={this.props.editGroupAreaInMap} />
</div>
: <div className={"advanced"}>
<DaySelection {...commonProps} {...dayProps} />
<label>{t("strings")}</label>
<Help text={t(ToolTips.DOT_NOTATION_TIP)} />
<EqCriteriaSelection<string> {...commonProps}
type={"string"} eqCriteria={criteria.string_eq}
criteriaKey={"string_eq"} />
<label>{t("numbers")}</label>
<EqCriteriaSelection<number> {...commonProps}
type={"number"} eqCriteria={criteria.number_eq}
criteriaKey={"number_eq"} />
<NumberCriteriaSelection {...commonProps} criteriaKey={"number_lt"} />
<NumberCriteriaSelection {...commonProps} criteriaKey={"number_gt"} />
</div>}
</div>;
}
}
/** Reset all group criteria to defaults. */
const ClearCriteria = (props: ClearCriteriaProps) =>
<button className="clear-criteria fb-button red"
title={t("clear all filters")}
onClick={() => {
if (confirm(t("Clear all group filters?"))) {
props.dispatch(overwriteGroup(props.group, {
...props.group.body, criteria: DEFAULT_CRITERIA
}));
}
}}>
{t("clear")}
</button>;
/** Clear manually selected points. */
const ClearPointIds = (props: ClearPointIdsProps) =>
<button className="clear-point-ids fb-button red"
title={t("clear manual selections")}
onClick={() => {
if (confirm(t("Remove all manual selections?"))) {
props.dispatch(overwriteGroup(props.group, {
...props.group.body, point_ids: []
}));
props.dispatch(selectPoint(undefined));
}
}}>
{t("clear")}
</button>;
/** Show counts of manual and criteria selections. */
export const GroupPointCountBreakdown =
(props: GroupPointCountBreakdownProps) => {
const manuallyAddedIds = props.group.body.point_ids;
const sortedPoints =
sortGroupBy(props.group.body.sort_type, props.pointsSelectedByGroup);
const manualPoints = sortedPoints
.filter(p => manuallyAddedIds.includes(p.body.id || 0));
const criteriaPoints = sortedPoints
.filter(p => !manuallyAddedIds.includes(p.body.id || 0));
const generatePointIcons = (point: TaggedPoint) =>
<PointGroupItem
key={point.uuid}
hovered={point.uuid === props.hovered}
group={props.group}
point={point}
dispatch={props.dispatch} />;
return <div className={"group-member-count-breakdown"}>
<div className={"manual-group-member-count"}>
<p>{`${manualPoints.length} ${t("manually selected")}`}</p>
<ClearPointIds dispatch={props.dispatch} group={props.group} />
</div>
{props.iconDisplay && manualPoints.length > 0 &&
<div className="groups-list-wrapper">
{manualPoints.map(generatePointIcons)}
</div>}
{props.shouldDisplay(Feature.criteria_groups) &&
<div className={"group-member-section"}>
<div className={"criteria-group-member-count"}>
<p>{`${criteriaPoints.length} ${t("selected by filters")}`}</p>
<ClearCriteria dispatch={props.dispatch} group={props.group} />
</div>
{props.iconDisplay && criteriaPoints.length > 0 &&
<div className="groups-list-wrapper">
{criteriaPoints.map(generatePointIcons)}
</div>}
</div>}
</div>;
};
/** Select pointer_type string equal criteria,
* which determines if any additional criteria is shown. */
export const PointTypeSelection = (props: PointTypeSelectionProps) =>
<div className={"point-type-selection"}>
<p className={"category"}>{t("Select all")}</p>
<FBSelect
key={JSON.stringify(props.group.body)}
list={POINTER_TYPE_LIST().slice(0, -1)}
customNullLabel={t("Select one")}
selectedItem={props.pointTypes[0]
? POINTER_TYPE_DDI_LOOKUP()[props.pointTypes[0]]
: undefined}
onChange={ddi => {
if (isPointType(ddi.value)) {
props.dispatch(togglePointTypeCriteria(props.group, ddi.value, true));
props.dispatch(setSelectionPointType([ddi.value]));
}
}} />
{props.pointTypes.length > 1 &&
POINTER_TYPES.map(pointerType =>
<div className="point-type-section" key={pointerType}>
<Checkbox
onChange={() =>
props.dispatch(togglePointTypeCriteria(props.group, pointerType))}
checked={props.pointTypes.includes(pointerType)}
title={CRITERIA_POINT_TYPE_LOOKUP()[pointerType]} />
<p>{CRITERIA_POINT_TYPE_LOOKUP()[pointerType]}</p>
</div>)}
</div>;