Skip to content

Commit

Permalink
feat: allow options to assign many flags (#3820)
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicamcinchak authored Nov 1, 2024
1 parent 9a9acf0 commit 8c5f127
Show file tree
Hide file tree
Showing 13 changed files with 745 additions and 291 deletions.
41 changes: 21 additions & 20 deletions editor.planx.uk/src/@planx/components/Checklist/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import InputRow from "ui/shared/InputRow";
import InputRowItem from "ui/shared/InputRowItem";

import { Option, parseBaseNodeData } from "../shared";
import { FlagsSelect } from "../shared/FlagsSelect";
import { ICONS } from "../shared/icons";
import PermissionSelect from "../shared/PermissionSelect";
import type { Checklist, Group } from "./model";
import { toggleExpandableChecklist } from "./model";
import { ChecklistProps, OptionEditorProps } from "./types";
Expand Down Expand Up @@ -68,20 +68,6 @@ const OptionEditor: React.FC<OptionEditorProps> = (props) => {
}}
/>

<PermissionSelect
value={props.value.data.flag || ""}
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
flag: ev.target.value as string,
},
});
}}
sx={{ width: { md: "160px" }, maxWidth: "160px" }}
/>

{typeof props.index !== "undefined" &&
props.groups &&
props.onMoveToGroup && (
Expand Down Expand Up @@ -117,6 +103,23 @@ const OptionEditor: React.FC<OptionEditorProps> = (props) => {
/>
</InputRow>
)}

<FlagsSelect
value={
Array.isArray(props.value.data.flag)
? props.value.data.flag
: [props.value.data.flag]
}
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
flag: ev,
},
});
}}
/>
</div>
);
};
Expand Down Expand Up @@ -171,7 +174,6 @@ const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
text: "",
description: "",
val: "",
flag: "",
},
}) as Option
}
Expand Down Expand Up @@ -245,7 +247,6 @@ const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
text: "",
description: "",
val: "",
flag: "",
},
}) as Option
}
Expand Down Expand Up @@ -295,9 +296,9 @@ export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
...values,
...(groupedOptions
? {
categories: groupedOptions.map((gr) => ({
title: gr.title,
count: gr.children.length,
categories: groupedOptions.map((group) => ({
title: group.title,
count: group.children.length,
})),
}
: {
Expand Down
32 changes: 14 additions & 18 deletions editor.planx.uk/src/@planx/components/Question/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import InputRowItem from "ui/shared/InputRowItem";
import { InternalNotes } from "../../../ui/editor/InternalNotes";
import { MoreInformation } from "../../../ui/editor/MoreInformation/MoreInformation";
import { BaseNodeData, Option, parseBaseNodeData } from "../shared";
import { FlagsSelect } from "../shared/FlagsSelect";
import { ICONS } from "../shared/icons";
import PermissionSelect from "../shared/PermissionSelect";

interface Props {
node: {
Expand Down Expand Up @@ -62,7 +62,6 @@ const OptionEditor: React.FC<{
placeholder="Option"
/>
</InputRowItem>

<ImgInput
img={props.value.data.img}
onChange={(img) => {
Expand All @@ -75,20 +74,6 @@ const OptionEditor: React.FC<{
});
}}
/>

<PermissionSelect
value={props.value.data.flag || ""}
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
flag: ev.target.value as string,
},
});
}}
sx={{ width: { md: "160px" }, maxWidth: "160px" }}
/>
</InputRow>
<InputRow>
<Input
Expand Down Expand Up @@ -123,6 +108,18 @@ const OptionEditor: React.FC<{
/>
</InputRow>
)}
<FlagsSelect
value={Array.isArray(props.value.data.flag) ? props.value.data.flag : [props.value.data.flag]}
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
flag: ev,
},
});
}}
/>
</div>
);

Expand Down Expand Up @@ -154,7 +151,7 @@ export const Question: React.FC<Props> = (props) => {
alert(JSON.stringify({ type, ...values, children }, null, 2));
}
},
validate: () => {},
validate: () => { },
});

const focusRef = useRef<HTMLInputElement | null>(null);
Expand Down Expand Up @@ -237,7 +234,6 @@ export const Question: React.FC<Props> = (props) => {
text: "",
description: "",
val: "",
flag: "",
},
}) as Option
}
Expand Down
87 changes: 87 additions & 0 deletions editor.planx.uk/src/@planx/components/shared/FlagsSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
AutocompleteChangeReason,
AutocompleteProps,
} from "@mui/material/Autocomplete";
import Chip from "@mui/material/Chip";
import ListItem from "@mui/material/ListItem";
import { Flag, flatFlags } from "@opensystemslab/planx-core/types";
import React, { useMemo } from "react";
import InputRow from "ui/shared/InputRow";
import { CustomCheckbox, SelectMultiple } from "ui/shared/SelectMultiple";

interface Props {
value?: Array<Flag["value"]>;
onChange: (values: Array<Flag["value"]>) => void;
}

const renderOptions: AutocompleteProps<
Flag,
true,
true,
false,
"div"
>["renderOption"] = (props, flag, { selected }) => (
<ListItem {...props}>
<CustomCheckbox
aria-hidden="true"
className={selected ? "selected" : ""}
sx={{ backgroundColor: `${flag.bgColor}` }}
/>
{flag.text}
</ListItem>
);

const renderTags: AutocompleteProps<
Flag,
true,
true,
false,
"div"
>["renderTags"] = (value, getFlagProps) =>
value.map((flag, index) => (
<Chip
{...getFlagProps({ index })}
key={flag.value}
label={flag.text}
sx={{ backgroundColor: flag.bgColor, color: flag.color }}
/>
));

export const FlagsSelect: React.FC<Props> = (props) => {
const { value: initialFlagValues } = props;

const value: Flag[] | undefined = useMemo(
() =>
initialFlagValues?.flatMap((initialFlagValue) =>
flatFlags.filter((flag) => flag.value === initialFlagValue),
),
[initialFlagValues],
);

const handleChange = (
_event: React.SyntheticEvent,
value: Flag[],
_reason: AutocompleteChangeReason,
) => {
const selectedFlags = value.map((flag) => flag.value);
props.onChange(selectedFlags);
};

return (
<InputRow>
<SelectMultiple
id="select-multiple-flags"
key="select-multiple-flags"
label="Flags (up to one per category)"
options={flatFlags}
getOptionLabel={(flag) => flag.text}
groupBy={(flag) => flag.category}
onChange={handleChange}
isOptionEqualToValue={(flag, value) => flag.value === value.value}
value={value}
renderOption={renderOptions}
renderTags={renderTags}
/>
</InputRow>
);
};
40 changes: 0 additions & 40 deletions editor.planx.uk/src/@planx/components/shared/PermissionSelect.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions editor.planx.uk/src/@planx/components/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NodeTags } from "@opensystemslab/planx-core/types";
import { Flag, NodeTags } from "@opensystemslab/planx-core/types";
import trim from "lodash/trim";
import { Store } from "pages/FlowEditor/lib/store";

Expand Down Expand Up @@ -28,7 +28,7 @@ export interface Option {
id: string;
data: {
description?: string;
flag?: string;
flag?: Array<Flag["value"]>;
img?: string;
text: string;
val?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Box from "@mui/material/Box";
import { Flag } from "@opensystemslab/planx-core/types";
import React from "react";

export const FlagBand: React.FC<{
flag: Flag;
}> = ({ flag }) => {
return (
<Box
sx={(theme) => ({
backgroundColor: `${flag?.bgColor || theme.palette.grey[800]}`,
borderBottom: `1px solid ${theme.palette.grey[400]}`,
width: "100%",
height: "12px",
})}
/>
);
};

export const NoFlagBand: React.FC = () => {
return (
<Box
sx={(theme) => ({
backgroundColor: theme.palette.grey[700],
borderBottom: `1px solid ${theme.palette.grey[400]}`,
width: "100%",
height: "12px",
})}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { flatFlags } from "@opensystemslab/planx-core/types";
import { Flag, flatFlags } from "@opensystemslab/planx-core/types";
import classNames from "classnames";
import React from "react";
import { Link } from "react-navi";
Expand All @@ -8,21 +8,31 @@ import { DataField } from "./DataField";
import Hanger from "./Hanger";
import Node from "./Node";
import { Thumbnail } from "./Thumbnail";
import { FlagBand, NoFlagBand } from "./FlagBand";

const Option: React.FC<any> = (props) => {
const childNodes = useStore((state) => state.childNodesOf(props.id));

const href = "";

let background = "#666"; // no flag color
let color = "#000";
let flags: Flag[] | undefined;

try {
const flag = flatFlags.find(({ value }) =>
[props.data?.flag, props.data?.val].filter(Boolean).includes(value),
);
background = flag?.bgColor || background;
color = flag?.color || color;
// Question & Checklist Options set zero or many flag values under "data.flag"
if (props.data?.flag) {
if (Array.isArray(props.data?.flag)) {
flags = flatFlags.filter(({ value }) => props.data?.flag?.includes(value));
} else {
flags = flatFlags.filter(({ value }) => props.data?.flag === value);
}
}

// Filter Options set single flag value under "data.val" (Questions & Checklists use this same field for passport values)
if (props.data?.val) {
const flagValues = flatFlags.map((flag) => flag.value).filter(Boolean);
if (flagValues.includes(props.data.val)) {
flags = flatFlags.filter(({ value }) => props.data.val === value);
}
}
} catch (e) {}

return (
Expand All @@ -36,7 +46,7 @@ const Option: React.FC<any> = (props) => {
imageAltText={props.data.text}
/>
)}
<div className="band" style={{ background, color }}></div>
{flags ? flags.map((flag) => <FlagBand key={`${props.id}-${flag.value}`} flag={flag} />) : <NoFlagBand />}
<div className="text">{props.data.text}</div>
{props.data?.val && <DataField value={props.data.val} variant="child" />}
</Link>
Expand Down
Loading

0 comments on commit 8c5f127

Please sign in to comment.