Skip to content

Commit

Permalink
chore(meshconfig): fix form
Browse files Browse the repository at this point in the history
  • Loading branch information
selankon committed Oct 29, 2024
1 parent c0dda22 commit bb7e94d
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 284 deletions.
173 changes: 173 additions & 0 deletions plugins/lime-plugin-mesh-wide-config/src/components/FormEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Trans, t } from "@lingui/macro";
import { StateUpdater, useEffect, useState } from "preact/hooks";
import { Controller, useFormContext } from "react-hook-form";
import { v4 as uuidv4 } from "uuid";

import { useDisclosure } from "components/Modal/useDisclosure";
import { Button, ButtonProps } from "components/buttons/button";
import InputField from "components/inputs/InputField";
import { useToast } from "components/toast/toastProvider";

import { EditOrDelete } from "plugins/lime-plugin-mesh-wide-config/src/components/Components";
import {
AddNewSectionFormProps,
AddNewSectionModal,
} from "plugins/lime-plugin-mesh-wide-config/src/components/modals";
import { IMeshWideConfig } from "plugins/lime-plugin-mesh-wide-config/src/meshConfigTypes";

export const EditableField = ({
isList,
name,
setIsEditing,
}: {
isList: boolean;
name: string;
setIsEditing?: StateUpdater<boolean>;
}) => {
const { control, setValue, watch, getValues } = useFormContext();

const value = watch(name);
const [initialState] = useState(value);
// Hack to force re-render when the list changes
const [uniqueKeys, setUniqueKeys] = useState(
isList ? value.map(() => uuidv4()) : ""
);

const removeListItem = (index) => {
const updatedValues = value.filter((_, i) => i !== index);
setValue(name, updatedValues); // Update form values
setUniqueKeys((keys) => keys.filter((_, i) => i !== index)); // Update keys to match the new array
};

const addListItem = () => {
setValue(name, [...value, ""]); // Update form values
setUniqueKeys((keys) => [...keys, uuidv4()]); // Add a new unique key
};

// Ensure the list has at least one item at the start
useEffect(() => {
if (isList && value.length === 0) {
setValue(name, [""]);
setUniqueKeys([uuidv4()]); // Reset keys for new list
}
}, [isList, value, name, setValue]);

if (isList) {
return (
<div key={name} className={"flex flex-col gap-6"}>
{uniqueKeys.map((item, index) => (
<Controller
key={uniqueKeys[index]} // Use the unique key
control={control}
name={`${name}[${index}]`}
rules={{
minLength: {
value: 1,
message: t`Minimum length is 1`,
},
required: t`This field cannot be empty`,
}}
render={({
field: { value, ...rest },
fieldState: { error },
}) => {
return (
<div
className={
"flex flex-row justify-center align-items-center gap-4"
}
>
<InputField
id={`${name}[${index}]`}
className="w-100"
value={value}
error={error?.message}
{...rest}
/>
<EditOrDelete
onDelete={() => removeListItem(index)}
/>
</div>
);
}}
/>
))}
<AddElementButton onClick={addListItem} />
</div>
);
}

return (
<Controller
name={name}
control={control}
rules={{
minLength: {
value: 1,
message: t`Minimum length is 1`,
},
required: t`This field cannot be empty`,
}}
render={({ field, fieldState: { error } }) => (
<InputField
id={name}
label={<Trans>Value</Trans>}
className="w-100"
error={error?.message}
{...field}
/>
)}
/>
);
};

export const AddNewConfigSection = ({
sectionName,
}: {
sectionName?: string;
}) => {
const { open, onOpen, onClose } = useDisclosure();
const { showToast } = useToast();

const { watch, setValue } = useFormContext<IMeshWideConfig>();
const section = watch(sectionName);

const onSuccess = (data: AddNewSectionFormProps) => {
if (!sectionName) {
setValue(data.name, {});
} else {
let value: string | string[] = data.value;
if (data.isList) {
value = data.values;
}
setValue(sectionName, {
...section,
[data.name]: value,
});
}
onClose();
showToast({
text: <Trans>Added section {data.name}</Trans>,
});
};

return (
<>
<AddElementButton onClick={onOpen} />
<AddNewSectionModal
sectionName={sectionName}
isOpen={open}
onSuccess={onSuccess}
onClose={onClose}
/>
</>
);
};

export const AddElementButton = (props: ButtonProps) => {
return (
<Button color={"info"} {...props}>
<Trans>Add new section</Trans>
</Button>
);
};
124 changes: 124 additions & 0 deletions plugins/lime-plugin-mesh-wide-config/src/components/FormOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Trans } from "@lingui/macro";
import { useState } from "preact/hooks";
import { FormProvider, useForm, useFormContext } from "react-hook-form";

import { useDisclosure } from "components/Modal/useDisclosure";
import { Button } from "components/buttons/button";
import Divider from "components/divider";
import { useToast } from "components/toast/toastProvider";

import { EditOrDelete } from "plugins/lime-plugin-mesh-wide-config/src/components/Components";
import { EditableField } from "plugins/lime-plugin-mesh-wide-config/src/components/FormEdit";
import { DeletePropModal } from "plugins/lime-plugin-mesh-wide-config/src/components/modals";
import { IMeshWideConfig } from "plugins/lime-plugin-mesh-wide-config/src/meshConfigTypes";

type OptionContainerProps = {
sectionName: string;
keyString: string;
};

export const OptionContainer = ({
keyString,
sectionName,
}: OptionContainerProps) => {
const { watch, setValue } = useFormContext();
const [isEditing, setIsEditing] = useState(false);
const {
open: isDeleteModalOpen,
onOpen: openDeleteModal,
onClose: onCloseDeleteModal,
} = useDisclosure();

const { showToast } = useToast();

const onDelete = async () => {
const newValues = { ...section };
delete newValues[keyString];
setValue(sectionName, newValues);
onCloseDeleteModal();
showToast({
text: <Trans>Deleted {keyString}</Trans>,
onAction: () => {
setValue(sectionName, section);
},
});
};

const name = `${sectionName}[${keyString}]`;
const value = watch(name);
const section = watch(sectionName);

let _value = value;
const isList = Array.isArray(value);
if (isList) {
_value = value.join(", ");
}

const fmethods = useForm<IMeshWideConfig>({
defaultValues: { [sectionName]: { [keyString]: value } },
});

const onSubmit = (data: IMeshWideConfig) => {
const newSectionValues = { ...section, ...data[sectionName] };
setValue(sectionName, newSectionValues);
setIsEditing(false);
};

return (
<div class={"px-4"}>
<div className={"flex justify-center"}>
<Divider color={"white"} />
</div>
<div className="pl-6 pr-4 py-6 flex flex-col gap-4">
<div className={"flex flex-row items-center justify-between"}>
<div>
{isList && <Trans>(List)</Trans>} {keyString}
</div>
<EditOrDelete
onEdit={() => setIsEditing((prev) => !prev)}
onDelete={openDeleteModal}
/>
</div>
{!isEditing && <div>{_value}</div>}
{isEditing && (
<FormProvider {...fmethods}>
<form>
<EditableField
isList={isList}
name={name}
setIsEditing={setIsEditing}
/>
<div className={"flex flex-row gap-4"}>
<Button
onClick={(e) => {
e.preventDefault();
fmethods.handleSubmit(onSubmit)();
}}
outline={true}
>
<Trans>Done</Trans>
</Button>
<Button
color={"danger"}
onClick={() => {
fmethods.reset();
setIsEditing(false);
}}
outline={true}
>
<Trans>Cancel</Trans>
</Button>
</div>
</form>
</FormProvider>
)}
</div>
<DeletePropModal
prop={keyString}
isOpen={isDeleteModalOpen}
onDelete={onDelete}
onClose={onCloseDeleteModal}
/>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ import { Trans } from "@lingui/macro";
import { useFormContext } from "react-hook-form";

import { useDisclosure } from "components/Modal/useDisclosure";
import { Button, ButtonProps } from "components/buttons/button";
import { Collapsible } from "components/collapsible";
import { useToast } from "components/toast/toastProvider";

import { EditOrDelete } from "plugins/lime-plugin-mesh-wide-config/src/components/Components";
import { OptionContainer } from "plugins/lime-plugin-mesh-wide-config/src/components/OptionForm";
import {
AddElementButton,
AddNewConfigSection,
} from "plugins/lime-plugin-mesh-wide-config/src/components/FormEdit";
import { OptionContainer } from "plugins/lime-plugin-mesh-wide-config/src/components/FormOption";
import {
AddNewSectionFormProps,
AddNewSectionModal,
DeletePropModal,
EditPropModal,
} from "plugins/lime-plugin-mesh-wide-config/src/components/modals";
import {
IMeshWideConfig,
IMeshWideSection,
} from "plugins/lime-plugin-mesh-wide-config/src/meshConfigTypes";
import { IMeshWideSection } from "plugins/lime-plugin-mesh-wide-config/src/meshConfigTypes";

export const ConfigSection = ({
export const FormSection = ({
title,
dropdown,
}: {
Expand Down Expand Up @@ -105,54 +105,3 @@ export const SectionEditOrDelete = ({ name }) => {
</>
);
};

export const AddNewConfigSection = ({
sectionName,
}: {
sectionName?: string;
}) => {
const { open, onOpen, onClose } = useDisclosure();
const { showToast } = useToast();

const { watch, setValue } = useFormContext<IMeshWideConfig>();
const section = watch(sectionName);

const onSuccess = (data: AddNewSectionFormProps) => {
if (!sectionName) {
setValue(data.name, {});
} else {
let value: string | string[] = data.value;
if (data.isList) {
value = data.values;
}
setValue(sectionName, {
...section,
[data.name]: value,
});
}
onClose();
showToast({
text: <Trans>Added section {data.name}</Trans>,
});
};

return (
<>
<AddElementButton onClick={onOpen} />
<AddNewSectionModal
sectionName={sectionName}
isOpen={open}
onSuccess={onSuccess}
onClose={onClose}
/>
</>
);
};

export const AddElementButton = (props: ButtonProps) => {
return (
<Button color={"info"} {...props}>
<Trans>Add new section</Trans>
</Button>
);
};
Loading

0 comments on commit bb7e94d

Please sign in to comment.