206 lines
7.9 KiB
TypeScript
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>;
|