Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: checklist component subfolders #4038

Merged
merged 4 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor editor
  • Loading branch information
jamdelion committed Dec 4, 2024
commit b94ef405bcbe44152dcc8ec8c59d5dd720a024cf
429 changes: 0 additions & 429 deletions editor.planx.uk/src/@planx/components/Checklist/Editor.tsx

This file was deleted.

189 changes: 189 additions & 0 deletions editor.planx.uk/src/@planx/components/Checklist/Editor/Editor.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply moved within an Editor subfolder to be co-located with related components

Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
import { FormikErrors, FormikValues, useFormik } from "formik";
import React, { useEffect, useRef } from "react";
import ImgInput from "ui/editor/ImgInput/ImgInput";
import InputGroup from "ui/editor/InputGroup";
import { ModalFooter } from "ui/editor/ModalFooter";
import ModalSection from "ui/editor/ModalSection";
import ModalSectionContent from "ui/editor/ModalSectionContent";
import RichTextInput from "ui/editor/RichTextInput/RichTextInput";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";
import { Switch } from "ui/shared/Switch";

import { parseBaseNodeData } from "../../shared";
import { ICONS } from "../../shared/icons";
import type { Checklist } from "../model";
import { toggleExpandableChecklist } from "../model";
import { ChecklistProps } from "../types";
import { Options } from "./components/Options";

export const ChecklistComponent: React.FC<ChecklistProps> = (props) => {
const type = TYPES.Checklist;

const formik = useFormik<Checklist>({
initialValues: {
allRequired: props.node?.data?.allRequired || false,
neverAutoAnswer: props.node?.data?.neverAutoAnswer || false,
description: props.node?.data?.description || "",
fn: props.node?.data?.fn || "",
groupedOptions: props.groupedOptions,
img: props.node?.data?.img || "",
options: props.options,
text: props.node?.data?.text || "",
...parseBaseNodeData(props.node?.data),
},
onSubmit: ({ options, groupedOptions, ...values }) => {
const sourceOptions = options?.length
? options
: groupedOptions?.flatMap((group) => group.children);

const filteredOptions = (sourceOptions || []).filter(
(option) => option.data.text,
);

const processedOptions = filteredOptions.map((option) => ({
...option,
id: option.id || undefined,
type: TYPES.Answer,
}));

if (props.handleSubmit) {
props.handleSubmit(
{
type,
data: {
...values,
...(groupedOptions
? {
categories: groupedOptions.map((group) => ({
title: group.title,
count: group.children.length,
})),
}
: {
categories: undefined,
}),
},
},
processedOptions,
);
} else {
alert(JSON.stringify({ type, ...values, options }, null, 2));
}
},
validate: ({ options, ...values }) => {
const errors: FormikErrors<FormikValues> = {};
if (values.fn && !options?.some((option) => option.data.val)) {
errors.fn =
"At least one option must set a data value when the checklist has a data field";
}
return errors;
},
});

const focusRef = useRef<HTMLInputElement | null>(null);

// horrible hack to remove focus from Rich Text Editor
useEffect(() => {
setTimeout(() => {
(document.activeElement as any).blur();
focusRef.current?.focus();
}, 50);
}, []);

return (
<form onSubmit={formik.handleSubmit} id="modal">
<ModalSection>
<ModalSectionContent title="Checklist" Icon={ICONS[type]}>
<InputGroup>
<InputRow>
<Input
format="large"
name="text"
value={formik.values.text}
placeholder="Text"
onChange={formik.handleChange}
inputRef={focusRef}
required
/>

<ImgInput
img={formik.values.img}
onChange={(newUrl) => {
formik.setFieldValue("img", newUrl);
}}
/>
</InputRow>

<InputRow>
<RichTextInput
name="description"
value={formik.values.description}
placeholder="Description"
onChange={formik.handleChange}
/>
</InputRow>

<InputRow>
<Input
format="data"
name="fn"
value={formik.values.fn}
placeholder="Data Field"
onChange={formik.handleChange}
error={Boolean(formik.errors?.fn)}
errorMessage={formik.errors?.fn}
/>
</InputRow>
<InputRow>
<Switch
checked={!!formik.values.groupedOptions}
onChange={() =>
formik.setValues({
...formik.values,
...toggleExpandableChecklist({
options: formik.values.options,
groupedOptions: formik.values.groupedOptions,
}),
})
}
label="Expandable"
/>
</InputRow>
<InputRow>
<Switch
checked={formik.values.allRequired}
onChange={() =>
formik.setFieldValue(
"allRequired",
!formik.values.allRequired,
)
}
label="All required"
/>
</InputRow>
<InputRow>
<Switch
checked={formik.values.neverAutoAnswer}
onChange={() =>
formik.setFieldValue(
"neverAutoAnswer",
!formik.values.neverAutoAnswer,
)
}
label="Always put to user (forgo automation)"
/>
</InputRow>
</InputGroup>
</ModalSectionContent>

<Options formik={formik} />
</ModalSection>

<ModalFooter formik={formik} />
</form>
);
};

export default ChecklistComponent;
jamdelion marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import Delete from "@mui/icons-material/Delete";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import adjust from "ramda/src/adjust";
import compose from "ramda/src/compose";
import remove from "ramda/src/remove";
import React from "react";
import { FormikHookReturn } from "types";
import ListManager from "ui/editor/ListManager/ListManager";
import ModalSectionContent from "ui/editor/ModalSectionContent";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";

import { Option } from "../../../shared";
import type { Group } from "../../model";
import { OptionEditor } from "./OptionsEditor";

export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
return (
<ModalSectionContent subtitle="Options">
{formik.values.groupedOptions ? (
<Box>
{formik.values.groupedOptions.map(
(groupedOption: Group<Option>, groupIndex: number) => (
<Box key={groupIndex} mt={groupIndex === 0 ? 0 : 4}>
<Box display="flex" pb={1}>
<InputRow>
<Input
required
format="bold"
name={`groupedOptions[${groupIndex}].title`}
value={groupedOption.title}
placeholder="Section Title"
onChange={formik.handleChange}
/>
</InputRow>
<Box flex={0}>
<IconButton
title="Delete group"
aria-label="Delete group"
onClick={() => {
formik.setFieldValue(
`groupedOptions`,
remove(groupIndex, 1, formik.values.groupedOptions),
);
}}
size="large"
>
<Delete />
</IconButton>
</Box>
</Box>
<Box pl={{ md: 2 }}>
<ListManager
values={groupedOption.children}
onChange={(newOptions) => {
formik.setFieldValue(
`groupedOptions[${groupIndex}].children`,
newOptions,
);
}}
newValue={() =>
({
data: {
text: "",
description: "",
val: "",
},
}) as Option
}
newValueLabel="add new option"
Editor={OptionEditor}
editorExtraProps={{
groupIndex,
showValueField: !!formik.values.fn,
onMoveToGroup: (
movedItemIndex: number,
moveToGroupIndex: number,
) => {
const item = groupedOption.children[movedItemIndex];
formik.setFieldValue(
"groupedOptions",
compose(
adjust(
moveToGroupIndex,
(option: Group<Option>) => ({
...option,
children: [...option.children, item],
}),
),
adjust(groupIndex, (option: Group<Option>) => ({
...option,
children: remove(
movedItemIndex,
1,
option.children,
),
})),
)(formik.values.groupedOptions),
);
},
groups: formik.values.groupedOptions.map(
(opt: Group<Option>) => opt.title,
),
}}
/>
</Box>
</Box>
),
)}
<Box mt={1}>
<Button
size="large"
onClick={() => {
formik.setFieldValue(`groupedOptions`, [
...formik.values.groupedOptions,
{
title: "",
children: [],
},
]);
}}
>
add new group
</Button>
</Box>
</Box>
) : (
<ListManager
values={formik.values.options || []}
onChange={(newOptions) => {
formik.setFieldValue("options", newOptions);
}}
newValueLabel="add new option"
newValue={() =>
({
data: {
text: "",
description: "",
val: "",
},
}) as Option
}
Editor={OptionEditor}
editorExtraProps={{ showValueField: !!formik.values.fn }}
/>
)}
</ModalSectionContent>
);
};
Loading
Loading