Skip to content

Commit

Permalink
feat: setup MapAndLabel with React Context and schema input fields pe…
Browse files Browse the repository at this point in the history
…r map feature (#3501)
  • Loading branch information
jessicamcinchak authored Aug 27, 2024
1 parent 0d83c4d commit 7bbd1c0
Show file tree
Hide file tree
Showing 11 changed files with 2,146 additions and 2,139 deletions.
3,680 changes: 1,657 additions & 2,023 deletions editor.planx.uk/pnpm-lock.yaml

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion editor.planx.uk/src/@planx/components/MapAndLabel/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FormControl from "@mui/material/FormControl";
import MenuItem from "@mui/material/MenuItem";
import RadioGroup from "@mui/material/RadioGroup";
import { useTheme } from "@mui/material/styles";
import { ComponentType as TYPES } from "@opensystemslab/planx-core/types";
Expand All @@ -9,18 +10,21 @@ import InputGroup from "ui/editor/InputGroup";
import ModalSection from "ui/editor/ModalSection";
import ModalSectionContent from "ui/editor/ModalSectionContent";
import RichTextInput from "ui/editor/RichTextInput";
import SelectInput from "ui/editor/SelectInput";
import InputLabel from "ui/public/InputLabel";
import Input from "ui/shared/Input";
import InputRow from "ui/shared/InputRow";
import InputRowItem from "ui/shared/InputRowItem";
import InputRowLabel from "ui/shared/InputRowLabel";

import BasicRadio from "../shared/Radio/BasicRadio";
import { EditorProps, ICONS, InternalNotes, MoreInformation } from "../ui";
import { MapAndLabel, parseContent } from "./model";
import { Trees } from "./schemas/Trees";

type Props = EditorProps<TYPES.MapAndLabel, MapAndLabel>;

export const SCHEMAS = [{ name: "Trees", schema: Trees }];

export default MapAndLabelComponent;

function MapAndLabelComponent(props: Props) {
Expand Down Expand Up @@ -114,6 +118,30 @@ function MapAndLabelComponent(props: Props) {
</InputLabel>
</FormControl>
</ModalSectionContent>
<ModalSectionContent title="Schema">
<InputRow>
<InputRowItem>
<SelectInput
value={formik.values.schemaName}
onChange={(e) => {
formik.setFieldValue("schemaName", e.target.value);
formik.setFieldValue(
"schema",
SCHEMAS.find(
({ name }) => name === (e.target.value as string),
)?.schema,
);
}}
>
{SCHEMAS.map(({ name }) => (
<MenuItem key={name} value={name}>
{name}
</MenuItem>
))}
</SelectInput>
</InputRowItem>
</InputRow>
</ModalSectionContent>
</ModalSection>
<MoreInformation
changeField={formik.handleChange}
Expand Down
106 changes: 0 additions & 106 deletions editor.planx.uk/src/@planx/components/MapAndLabel/Public.tsx

This file was deleted.

155 changes: 155 additions & 0 deletions editor.planx.uk/src/@planx/components/MapAndLabel/Public/Context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { useSchema } from "@planx/components/shared/Schema/hook";
import {
Schema,
SchemaUserData,
SchemaUserResponse,
} from "@planx/components/shared/Schema/model";
import {
getPreviouslySubmittedData,
makeData,
} from "@planx/components/shared/utils";
import { PublicProps } from "@planx/components/ui";
import { FormikProps, useFormik } from "formik";
import React, {
createContext,
PropsWithChildren,
useContext,
useState,
} from "react";

import { MapAndLabel } from "../model";

interface MapAndLabelContextValue {
schema: Schema;
activeIndex: number;
saveItem: () => Promise<void>;
editItem: (index: number) => void;
cancelEditItem: () => void;
formik: FormikProps<SchemaUserData>;
validateAndSubmitForm: () => void;
mapAndLabelProps: PublicProps<MapAndLabel>;
errors: {
unsavedItem: boolean;
min: boolean;
max: boolean;
};
}

type MapAndLabelProviderProps = PropsWithChildren<PublicProps<MapAndLabel>>;

const MapAndLabelContext = createContext<MapAndLabelContextValue | undefined>(
undefined,
);

export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
props,
) => {
const { schema, children, handleSubmit } = props;
const { formikConfig, initialValues: _initialValues } = useSchema({
schema,
previousValues: getPreviouslySubmittedData(props),
});

const formik = useFormik<SchemaUserData>({
...formikConfig,
onSubmit: (values) => {
const defaultPassportData = makeData(props, values.schemaData)?.["data"];

handleSubmit?.({
data: {
...defaultPassportData,
},
});
},
});

const [activeIndex, setActiveIndex] = useState<number>(
props.previouslySubmittedData ? -1 : 0,
);

const [activeItemInitialState, setActiveItemInitialState] = useState<
SchemaUserResponse | undefined
>(undefined);

const [unsavedItemError, setUnsavedItemError] = useState<boolean>(false);
const [minError, setMinError] = useState<boolean>(false);
const [maxError, setMaxError] = useState<boolean>(false);

const resetErrors = () => {
setMinError(false);
setMaxError(false);
setUnsavedItemError(false);
};

const saveItem = async () => {
resetErrors();

const errors = await formik.validateForm();
const isValid = !errors.schemaData?.length;
if (isValid) {
exitEditMode();
}
};

const validateAndSubmitForm = () => {
// Do not allow submissions with an unsaved item
if (activeIndex !== -1) return setUnsavedItemError(true);

// Manually validate minimum number of items
if (formik.values.schemaData.length < schema.min) {
return setMinError(true);
}

formik.handleSubmit();
};

const cancelEditItem = () => {
if (activeItemInitialState) resetItemToPreviousState();

setActiveItemInitialState(undefined);

exitEditMode();
};

const editItem = (index: number) => {
setActiveItemInitialState(formik.values.schemaData[index]);
setActiveIndex(index);
};

const exitEditMode = () => setActiveIndex(-1);

const resetItemToPreviousState = () =>
formik.setFieldValue(`schemaData[${activeIndex}]`, activeItemInitialState);

return (
<MapAndLabelContext.Provider
value={{
activeIndex,
saveItem,
schema,
mapAndLabelProps: props,
editItem,
cancelEditItem,
formik,
validateAndSubmitForm,
errors: {
unsavedItem: unsavedItemError,
min: minError,
max: maxError,
},
}}
>
{children}
</MapAndLabelContext.Provider>
);
};

export const useMapAndLabelContext = (): MapAndLabelContextValue => {
const context = useContext(MapAndLabelContext);
if (!context) {
throw new Error(
"useMapAndLabelContext must be used within a MapAndLabelProvider",
);
}
return context;
};
Loading

0 comments on commit 7bbd1c0

Please sign in to comment.